summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp16
-rw-r--r--Android.bp1
-rw-r--r--apct-tests/perftests/core/src/android/os/TracePerfTest.java25
-rw-r--r--boot/boot-image-profile.txt1
-rw-r--r--boot/preloaded-classes1
-rw-r--r--cmds/uinput/src/com/android/commands/uinput/EvemuParser.java33
-rw-r--r--config/preloaded-classes1
-rw-r--r--core/api/module-lib-current.txt2
-rw-r--r--core/api/system-current.txt16
-rw-r--r--core/java/android/app/ApplicationStartInfo.java15
-rw-r--r--core/java/android/app/LocaleConfig.java27
-rw-r--r--core/java/android/app/StatusBarManager.java4
-rw-r--r--core/java/android/app/notification.aconfig10
-rw-r--r--core/java/android/app/supervision/SupervisionAppService.java38
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig7
-rw-r--r--core/java/android/content/pm/PackageParser.java18
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java31
-rw-r--r--core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java33
-rw-r--r--core/java/android/hardware/camera2/CameraManager.java3
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl2
-rw-r--r--core/java/android/hardware/input/IKeyGestureHandler.aidl10
-rw-r--r--core/java/android/hardware/input/InputManager.java21
-rw-r--r--core/java/android/hardware/input/InputManagerGlobal.java103
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig9
-rw-r--r--core/java/android/hardware/serial/flags/flags.aconfig2
-rw-r--r--core/java/android/net/VpnManager.java26
-rw-r--r--core/java/android/net/flags.aconfig8
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java4
-rw-r--r--core/java/android/os/PerfettoTrace.java15
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java67
-rw-r--r--core/java/android/os/image/flags/trade_in_mode_flags.aconfig9
-rw-r--r--core/java/android/provider/Settings.java10
-rw-r--r--core/java/android/util/ArrayMap.java2
-rw-r--r--core/java/android/view/InsetsController.java4
-rw-r--r--core/java/android/view/OrientationEventListener.java10
-rw-r--r--core/java/android/view/WindowManagerImpl.java2
-rw-r--r--core/java/android/widget/DatePickerCalendarDelegate.java48
-rw-r--r--core/java/android/widget/TimePickerClockDelegate.java52
-rw-r--r--core/java/android/window/DesktopModeFlags.java10
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig10
-rw-r--r--core/java/com/android/internal/jank/DisplayResolutionTracker.java17
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java10
-rw-r--r--core/java/com/android/internal/protolog/common/LogDataType.java4
-rw-r--r--core/java/com/android/internal/statusbar/DisableStates.aidl19
-rw-r--r--core/java/com/android/internal/statusbar/DisableStates.java95
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl2
-rw-r--r--core/java/com/android/internal/widget/ConversationLayout.java20
-rw-r--r--core/java/com/android/internal/widget/MessagingLayout.java8
-rw-r--r--core/java/com/android/server/servicewatcher/OWNERS2
-rw-r--r--core/jni/Android.bp2
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_media_AudioSystem.cpp30
-rw-r--r--core/jni/android_media_AudioVolumeGroupCallback.cpp176
-rw-r--r--core/jni/android_media_AudioVolumeGroupCallback.h44
-rw-r--r--core/res/res/values/config.xml6
-rw-r--r--core/res/res/values/config_telephony.xml21
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/FileSystemUtilsTest/OWNERS2
-rw-r--r--core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java14
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java95
-rw-r--r--core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt92
-rw-r--r--core/tests/coretests/src/com/android/internal/statusbar/DisableStatesTest.java64
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_restart.xml28
-rw-r--r--libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml6
-rw-r--r--libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml7
-rw-r--r--libs/WindowManager/Shell/res/values/colors.xml2
-rw-r--r--libs/WindowManager/Shell/res/values/dimen.xml13
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/res/values/dimen.xml4
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt6
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java122
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.kt96
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt28
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt70
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopUserRepositories.kt21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt81
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt48
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt88
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java184
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.kt153
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java29
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt20
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt131
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt49
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolderTest.kt5
-rw-r--r--libs/androidfw/AssetManager2.cpp4
-rw-r--r--libs/androidfw/ResourceTypes.cpp16
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h5
-rw-r--r--libs/hwui/jni/Bitmap.cpp4
-rw-r--r--libs/hwui/jni/GIFMovie.cpp10
-rw-r--r--libs/hwui/renderthread/ReliableSurface.cpp24
-rw-r--r--libs/hwui/renderthread/VulkanManager.cpp14
-rw-r--r--libs/hwui/renderthread/VulkanManager.h8
-rw-r--r--location/api/system-current.txt8
-rw-r--r--location/java/android/location/BeidouSatelliteEphemeris.java22
-rw-r--r--location/java/android/location/GalileoAssistance.java16
-rw-r--r--media/java/android/media/AudioManager.java96
-rw-r--r--media/java/android/media/AudioSystem.java21
-rw-r--r--media/java/android/media/IAudioService.aidl5
-rw-r--r--media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java166
-rw-r--r--media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl31
-rw-r--r--media/java/android/media/projection/TEST_MAPPING6
-rw-r--r--media/java/android/media/quality/MediaQualityContract.java38
-rw-r--r--media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java211
-rw-r--r--media/tests/projection/Android.bp8
-rw-r--r--media/tests/projection/AndroidManifest.xml2
-rw-r--r--media/tests/projection/AndroidTest.xml9
-rw-r--r--media/tests/projection/TEST_MAPPING7
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java293
-rw-r--r--packages/CredentialManager/wear/AndroidManifest.xml6
-rw-r--r--packages/CredentialManager/wear/res/values/themes.xml24
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java32
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt20
-rw-r--r--packages/SettingsLib/AdaptiveIcon/Android.bp3
-rw-r--r--packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java3
-rw-r--r--packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java2
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java4
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt5
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt11
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerProvider.kt53
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java71
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml3
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt1
-rw-r--r--packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt17
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt2
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml4
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt37
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt17
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java120
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt25
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt7
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerProviderTest.kt89
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java36
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CsipDeviceManagerTest.java4
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt10
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java22
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java6
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/Android.bp1
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java13
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java5
-rw-r--r--packages/SystemUI/aconfig/desktop_users_and_accounts.aconfig9
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig54
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt11
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt44
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt73
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt92
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt13
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt21
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt71
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt103
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java32
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt43
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt153
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt214
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt101
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt212
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt141
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt55
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt223
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt67
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt47
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt86
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java7
-rw-r--r--packages/SystemUI/res/drawable/unpin_icon.xml10
-rw-r--r--packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml2
-rw-r--r--packages/SystemUI/res/layout/activity_rear_display_enabled.xml10
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml3
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml3
-rw-r--r--packages/SystemUI/res/layout/promoted_permission_guts.xml73
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml7
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider_floating.xml4
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_top_section.xml6
-rw-r--r--packages/SystemUI/res/values/dimens.xml21
-rw-r--r--packages/SystemUI/res/values/strings.xml15
-rw-r--r--packages/SystemUI/shared/biometrics/Android.bp3
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java15
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java3
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/ScreenDecorations.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/AssistManager.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java50
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmReorderAtmsCalls.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt71
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt96
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerUI.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt229
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt146
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/ColorsModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java173
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/data/repository/ScreenTimeoutPolicyRepository.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/display/DisplayHelper.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt66
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt70
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt47
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java55
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt69
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt127
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt126
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt727
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/WindowManagerProviderImplTest.kt53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt11
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/utils/windowmanager/FakeWindowManagerProvider.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCsdWarningInteractorKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt)22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelKosmos.kt41
-rw-r--r--packages/SystemUI/utils/Android.bp2
-rw-r--r--packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProvider.kt56
-rw-r--r--packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProviderImpl.kt28
-rw-r--r--packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerUtils.kt49
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt2
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt4
-rw-r--r--ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt12
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java15
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java33
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java23
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/adb/AdbService.java26
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java7
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java41
-rw-r--r--services/core/java/com/android/server/audio/AudioSystemAdapter.java24
-rw-r--r--services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java103
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java109
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayAdapter.java26
-rw-r--r--services/core/java/com/android/server/display/OverlayDisplayWindow.java11
-rw-r--r--services/core/java/com/android/server/display/mode/DisplayModeDirector.java29
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java9
-rw-r--r--services/core/java/com/android/server/input/AppLaunchShortcutManager.java2
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java9
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java136
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java179
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java39
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java74
-rw-r--r--services/core/java/com/android/server/locksettings/Android.bp11
-rw-r--r--services/core/java/com/android/server/locksettings/RebootEscrowManager.java66
-rw-r--r--services/core/java/com/android/server/locksettings/flags.aconfig9
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java34
-rw-r--r--services/core/java/com/android/server/media/projection/Android.bp21
-rw-r--r--services/core/java/com/android/server/media/projection/TEST_MAPPING28
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityUtils.java258
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java4
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java3
-rw-r--r--services/core/java/com/android/server/pm/DeletePackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java3
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java3
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java13
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java20
-rw-r--r--services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java281
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java199
-rw-r--r--services/core/java/com/android/server/power/ThermalManagerService.java47
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java62
-rw-r--r--services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java26
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java91
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraOverrides.java3
-rw-r--r--services/core/java/com/android/server/wm/AppCompatCameraPolicy.java4
-rw-r--r--services/core/java/com/android/server/wm/DeviceStateAutoRotateSettingIssueLogger.java42
-rw-r--r--services/core/java/com/android/server/wm/Task.java49
-rw-r--r--services/core/java/com/android/server/wm/TaskFpsCallbackController.java5
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java18
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp5
-rw-r--r--services/credentials/java/com/android/server/credentials/MetricUtilities.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java8
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java16
-rw-r--r--services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java16
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt7
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java25
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java26
-rw-r--r--services/tests/displayservicetests/Android.bp1
-rw-r--r--services/tests/displayservicetests/AndroidManifest.xml1
-rw-r--r--services/tests/displayservicetests/AndroidTest.xml1
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java244
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java259
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java243
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt33
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java3
-rw-r--r--services/tests/servicestests/Android.bp3
-rw-r--r--services/tests/servicestests/AndroidTest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java18
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java23
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java142
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubEndpointTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java61
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java62
-rw-r--r--services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java29
-rw-r--r--services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java74
-rw-r--r--services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt50
-rw-r--r--services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java4
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/Android.bp38
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml27
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/OWNERS3
-rw-r--r--services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java87
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java148
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java129
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DeviceStateAutoRotateSettingIssueLoggerTests.java43
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java85
-rw-r--r--telecomm/java/android/telecom/Call.java60
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java10
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java83
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl15
-rw-r--r--tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt115
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt25
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt234
-rw-r--r--tests/Input/src/com/android/test/input/AnrTest.kt86
-rw-r--r--tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java2
-rw-r--r--tools/aapt2/ResourcesInternal.proto5
-rw-r--r--tools/aapt2/cmd/Compile.cpp43
-rw-r--r--tools/aapt2/cmd/Convert.h14
-rw-r--r--tools/aapt2/cmd/Link.cpp22
-rw-r--r--tools/aapt2/cmd/Link.h9
-rw-r--r--tools/aapt2/cmd/Optimize.h14
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp17
-rw-r--r--tools/aapt2/format/binary/TableFlattener_test.cpp122
-rw-r--r--tools/aapt2/format/proto/ProtoDeserialize.cpp1
-rw-r--r--tools/aapt2/format/proto/ProtoSerialize.cpp1
-rw-r--r--tools/aapt2/link/FlaggedResources_test.cpp6
-rw-r--r--tools/aapt2/link/FlaggedXmlVersioner.cpp48
-rw-r--r--tools/aapt2/link/FlaggedXmlVersioner_test.cpp4
-rw-r--r--tools/aapt2/link/TableMerger.cpp13
-rw-r--r--tools/aapt2/readme.md4
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/Exceptions.kt (renamed from tools/protologtool/src/com/android/protolog/tool/exceptions.kt)25
-rw-r--r--tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt14
-rw-r--r--tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt37
662 files changed, 13627 insertions, 5935 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index f9cc1259a375..b9537b357517 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -50,6 +50,7 @@ aconfig_declarations_group {
"android.hardware.devicestate.feature.flags-aconfig-java",
"android.hardware.flags-aconfig-java",
"android.hardware.radio.flags-aconfig-java",
+ "android.hardware.serial.flags-aconfig-java",
"android.hardware.usb.flags-aconfig-java",
"android.location.flags-aconfig-java",
"android.media.codec-aconfig-java",
@@ -1962,3 +1963,18 @@ java_aconfig_library {
aconfig_declarations: "android.service.selinux.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Serial
+aconfig_declarations {
+ name: "android.hardware.serial.flags-aconfig",
+ exportable: true,
+ package: "android.hardware.serial.flags",
+ container: "system",
+ srcs: ["core/java/android/hardware/serial/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.hardware.serial.flags-aconfig-java",
+ aconfig_declarations: "android.hardware.serial.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 127556f8e075..e7c20418bcef 100644
--- a/Android.bp
+++ b/Android.bp
@@ -427,6 +427,7 @@ java_defaults {
"framework-permission-aidl-java",
"spatializer-aidl-java",
"audiopolicy-aidl-java",
+ "volumegroupcallback-aidl-java",
"sounddose-aidl-java",
"modules-utils-expresslog",
"perfetto_trace_javastream_protos_jarjar",
diff --git a/apct-tests/perftests/core/src/android/os/TracePerfTest.java b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
index 00e1c1fdbf4b..3df708d1a5cd 100644
--- a/apct-tests/perftests/core/src/android/os/TracePerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/TracePerfTest.java
@@ -46,6 +46,7 @@ public class TracePerfTest {
private static final String FOO = "foo";
private static final Category FOO_CATEGORY = new Category(FOO);
+ private static final Category UNREGISTERED_CATEGORY = new Category("unregistered");
private static PerfettoTrace.Session sPerfettoSession;
@BeforeClass
@@ -163,6 +164,30 @@ public class TracePerfTest {
}
}
+ @Test
+ public void testInstantPerfettoWithProtoUnregistered() {
+ PerfettoTrace.begin(UNREGISTERED_CATEGORY, "message_queue_receive")
+ .beginProto()
+ .beginNested(2004 /* message_queue */)
+ .addField(1 /* sending_thread_name */, "foo")
+ .endNested()
+ .endProto()
+ .setTerminatingFlow(5)
+ .emit();
+
+ BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ PerfettoTrace.begin(UNREGISTERED_CATEGORY, "message_queue_receive")
+ .beginProto()
+ .beginNested(2004 /* message_queue */)
+ .addField(1 /* sending_thread_name */, "foo")
+ .endNested()
+ .endProto()
+ .setTerminatingFlow(5)
+ .emit();
+ }
+ }
+
private static TraceConfig getTraceConfig(String cat) {
BufferConfig bufferConfig = BufferConfig.newBuilder().setSizeKb(1024).build();
TrackEventConfig trackEventConfig = TrackEventConfig
diff --git a/boot/boot-image-profile.txt b/boot/boot-image-profile.txt
index d7c409fd54b4..4c3e5dce7a95 100644
--- a/boot/boot-image-profile.txt
+++ b/boot/boot-image-profile.txt
@@ -28829,7 +28829,6 @@ Landroid/media/audiopolicy/AudioProductStrategy$AudioAttributesGroup;
Landroid/media/audiopolicy/AudioProductStrategy;
Landroid/media/audiopolicy/AudioVolumeGroup$1;
Landroid/media/audiopolicy/AudioVolumeGroup;
-Landroid/media/audiopolicy/AudioVolumeGroupChangeHandler;
Landroid/media/audiopolicy/IAudioPolicyCallback$Stub$Proxy;
Landroid/media/audiopolicy/IAudioPolicyCallback$Stub;
Landroid/media/audiopolicy/IAudioPolicyCallback;
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index 7f4b3244c164..048687781774 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -5509,7 +5509,6 @@ android.media.audiopolicy.AudioProductStrategy$AudioAttributesGroup
android.media.audiopolicy.AudioProductStrategy
android.media.audiopolicy.AudioVolumeGroup$1
android.media.audiopolicy.AudioVolumeGroup
-android.media.audiopolicy.AudioVolumeGroupChangeHandler
android.media.audiopolicy.IAudioPolicyCallback$Stub$Proxy
android.media.audiopolicy.IAudioPolicyCallback$Stub
android.media.audiopolicy.IAudioPolicyCallback
diff --git a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
index da991624e685..d3e62d5351f0 100644
--- a/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
+++ b/cmds/uinput/src/com/android/commands/uinput/EvemuParser.java
@@ -48,12 +48,17 @@ public class EvemuParser implements EventParser {
private static class CommentAwareReader {
private final LineNumberReader mReader;
- private String mPreviousLine;
- private String mNextLine;
+ /** The previous line of the file, or {@code null} if we're at the start of the file. */
+ private @Nullable String mPreviousLine;
+ /**
+ * The next line of the file to be returned from {@link #peekLine()}, or {@code null} if we
+ * haven't peeked since the last {@link #advance()} or are at the end of the file.
+ */
+ private @Nullable String mNextLine;
+ private boolean mAtEndOfFile = false;
- CommentAwareReader(LineNumberReader in) throws IOException {
+ CommentAwareReader(LineNumberReader in) {
mReader = in;
- mNextLine = findNextLine();
}
private @Nullable String findNextLine() throws IOException {
@@ -61,7 +66,7 @@ public class EvemuParser implements EventParser {
while (line != null && line.length() == 0) {
String unstrippedLine = mReader.readLine();
if (unstrippedLine == null) {
- // End of file.
+ mAtEndOfFile = true;
return null;
}
line = stripComments(unstrippedLine);
@@ -85,22 +90,28 @@ public class EvemuParser implements EventParser {
* {@code null} if the end of the file is reached. However, it does not advance to the
* next line of the file.
*/
- public @Nullable String peekLine() {
+ public @Nullable String peekLine() throws IOException {
+ if (mNextLine == null && !mAtEndOfFile) {
+ mNextLine = findNextLine();
+ }
return mNextLine;
}
/** Moves to the next line of the file. */
- public void advance() throws IOException {
+ public void advance() {
mPreviousLine = mNextLine;
- mNextLine = findNextLine();
+ mNextLine = null;
}
public boolean isAtEndOfFile() {
- return mNextLine == null;
+ return mAtEndOfFile;
}
- /** Returns the previous line, for error messages. */
- public String getPreviousLine() {
+ /**
+ * Returns the previous line, for error messages. Will be {@code null} if we're at the start
+ * of the file.
+ */
+ public @Nullable String getPreviousLine() {
return mPreviousLine;
}
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 707acb00b102..3f0e00be75a7 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -5514,7 +5514,6 @@ android.media.audiopolicy.AudioProductStrategy$AudioAttributesGroup
android.media.audiopolicy.AudioProductStrategy
android.media.audiopolicy.AudioVolumeGroup$1
android.media.audiopolicy.AudioVolumeGroup
-android.media.audiopolicy.AudioVolumeGroupChangeHandler
android.media.audiopolicy.IAudioPolicyCallback$Stub$Proxy
android.media.audiopolicy.IAudioPolicyCallback$Stub
android.media.audiopolicy.IAudioPolicyCallback
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 526a213a6003..98570172e43c 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -304,6 +304,8 @@ package android.net {
field public static final int TYPE_VPN_LEGACY = 3; // 0x3
field public static final int TYPE_VPN_NONE = -1; // 0xffffffff
field public static final int TYPE_VPN_OEM = 4; // 0x4
+ field @FlaggedApi("android.net.platform.flags.vpn_type_oem_service_and_legacy") public static final int TYPE_VPN_OEM_LEGACY = 6; // 0x6
+ field @FlaggedApi("android.net.platform.flags.vpn_type_oem_service_and_legacy") public static final int TYPE_VPN_OEM_SERVICE = 5; // 0x5
field public static final int TYPE_VPN_PLATFORM = 2; // 0x2
field public static final int TYPE_VPN_SERVICE = 1; // 0x1
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2ce3609d77e7..42c60b0ba0da 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -2940,6 +2940,13 @@ package android.app.smartspace.uitemplatedata {
package android.app.supervision {
+ @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public class SupervisionAppService extends android.app.Service {
+ ctor public SupervisionAppService();
+ method @Nullable public final android.os.IBinder onBind(@Nullable android.content.Intent);
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onDisabled();
+ method @FlaggedApi("android.app.supervision.flags.enable_supervision_app_service") public void onEnabled();
+ }
+
@FlaggedApi("android.app.supervision.flags.supervision_manager_apis") public class SupervisionManager {
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public android.content.Intent createConfirmSupervisionCredentialsIntent();
method @FlaggedApi("android.app.supervision.flags.supervision_manager_apis") @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public boolean isSupervisionEnabled();
@@ -7435,7 +7442,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void muteAwaitConnection(@NonNull int[], @NonNull android.media.AudioDeviceAttributes, long, @NonNull java.util.concurrent.TimeUnit) throws java.lang.IllegalStateException;
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int registerAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void registerMuteAwaitConnectionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
- method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
+ method @FlaggedApi("android.media.audio.register_volume_callback_api_hardening") @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void removeOnDevicesForAttributesChangedListener(@NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
@@ -7466,7 +7473,7 @@ package android.media {
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
- method public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
+ method @FlaggedApi("android.media.audio.register_volume_callback_api_hardening") @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED") public void unregisterVolumeGroupCallback(@NonNull android.media.AudioManager.VolumeGroupCallback);
field public static final String ACTION_VOLUME_CHANGED = "android.media.VOLUME_CHANGED_ACTION";
field public static final int AUDIOFOCUS_FLAG_DELAY_OK = 1; // 0x1
field public static final int AUDIOFOCUS_FLAG_LOCK = 4; // 0x4
@@ -18621,6 +18628,7 @@ package android.telephony.satellite {
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void deprovisionService(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getAttachRestrictionReasonsForCarrier(int);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatelliteDataOptimizedApps();
+ method @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int getSatelliteDataSupportMode(int);
method @FlaggedApi("com.android.internal.telephony.flags.satellite_system_apis") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int[] getSatelliteDisallowedReasons();
method @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.List<java.lang.String> getSatellitePlmnsForCarrier(int);
method @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -18703,6 +18711,10 @@ package android.telephony.satellite {
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_SEND_SUCCESS = 2; // 0x2
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_UNKNOWN = -1; // 0xffffffff
field public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_WAITING_TO_CONNECT = 8; // 0x8
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final int SATELLITE_DATA_SUPPORT_CONSTRAINED = 1; // 0x1
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final int SATELLITE_DATA_SUPPORT_RESTRICTED = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final int SATELLITE_DATA_SUPPORT_UNCONSTRAINED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.satellite_25q4_apis") public static final int SATELLITE_DATA_SUPPORT_UNKNOWN = -1; // 0xffffffff
field public static final int SATELLITE_MODEM_STATE_CONNECTED = 7; // 0x7
field public static final int SATELLITE_MODEM_STATE_DATAGRAM_RETRYING = 3; // 0x3
field public static final int SATELLITE_MODEM_STATE_DATAGRAM_TRANSFERRING = 2; // 0x2
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index 2559bd036039..e2af7c03b5fa 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -1082,6 +1082,15 @@ public final class ApplicationStartInfo implements Parcelable {
final ApplicationStartInfo o = (ApplicationStartInfo) other;
+ boolean intentEquals = true;
+ if (android.content.flags.Flags.intentSaveToXmlPackage()) {
+ if (mStartIntent == null) {
+ intentEquals = o.mStartIntent == null;
+ } else {
+ intentEquals = mStartIntent.filterEquals(o.mStartIntent);
+ }
+ }
+
return mPid == o.mPid
&& mRealUid == o.mRealUid
&& mPackageUid == o.mPackageUid
@@ -1095,14 +1104,16 @@ public final class ApplicationStartInfo implements Parcelable {
&& timestampsEquals(o)
&& mWasForceStopped == o.mWasForceStopped
&& mMonotonicCreationTimeMs == o.mMonotonicCreationTimeMs
- && mStartComponent == o.mStartComponent;
+ && mStartComponent == o.mStartComponent
+ && intentEquals;
}
@Override
public int hashCode() {
return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
mStartType, mLaunchMode, mPackageName, mProcessName, mStartupTimestampsNs,
- mMonotonicCreationTimeMs, mStartComponent);
+ mMonotonicCreationTimeMs, mStartComponent,
+ android.content.flags.Flags.intentSaveToXmlPackage() ? mStartIntent : null);
}
private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index f56bf4d434e7..cbfd7fccb7c6 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -144,15 +144,12 @@ public class LocaleConfig implements Parcelable {
}
}
Resources res = context.getResources();
- //Get the resource id
int resId = context.getApplicationInfo().getLocaleConfigRes();
if (resId == 0) {
mStatus = STATUS_NOT_SPECIFIED;
return;
}
- try {
- //Get the parser to read XML data
- XmlResourceParser parser = res.getXml(resId);
+ try (XmlResourceParser parser = res.getXml(resId)) {
parseLocaleConfig(parser, res);
} catch (Resources.NotFoundException e) {
Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.");
@@ -208,22 +205,22 @@ public class LocaleConfig implements Parcelable {
String defaultLocale = null;
if (android.content.res.Flags.defaultLocale()) {
// Read the defaultLocale attribute of the LocaleConfig element
- TypedArray att = res.obtainAttributes(
- attrs, com.android.internal.R.styleable.LocaleConfig);
- defaultLocale = att.getString(
- R.styleable.LocaleConfig_defaultLocale);
- att.recycle();
+ try (TypedArray att = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig)) {
+ defaultLocale = att.getString(
+ R.styleable.LocaleConfig_defaultLocale);
+ }
}
Set<String> localeNames = new HashSet<>();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (TAG_LOCALE.equals(parser.getName())) {
- final TypedArray attributes = res.obtainAttributes(
- attrs, com.android.internal.R.styleable.LocaleConfig_Locale);
- String nameAttr = attributes.getString(
- com.android.internal.R.styleable.LocaleConfig_Locale_name);
- localeNames.add(nameAttr);
- attributes.recycle();
+ try (TypedArray attributes = res.obtainAttributes(
+ attrs, com.android.internal.R.styleable.LocaleConfig_Locale)) {
+ String nameAttr = attributes.getString(
+ com.android.internal.R.styleable.LocaleConfig_Locale_name);
+ localeNames.add(nameAttr);
+ }
} else {
XmlUtils.skipCurrentTag(parser);
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 01868cc601fe..927d46999284 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -58,8 +58,10 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.NotificationVisibility;
+import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -119,6 +121,7 @@ public class StatusBarManager {
| DISABLE_SEARCH | DISABLE_ONGOING_CALL_CHIP;
/** @hide */
+ @Target(ElementType.TYPE_USE)
@IntDef(flag = true, prefix = {"DISABLE_"}, value = {
DISABLE_NONE,
DISABLE_EXPAND,
@@ -161,6 +164,7 @@ public class StatusBarManager {
| DISABLE2_NOTIFICATION_SHADE | DISABLE2_GLOBAL_ACTIONS | DISABLE2_ROTATE_SUGGESTIONS;
/** @hide */
+ @Target(ElementType.TYPE_USE)
@IntDef(flag = true, prefix = { "DISABLE2_" }, value = {
DISABLE2_NONE,
DISABLE2_MASK,
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 6f0eafe487af..d764c58b2b1e 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -385,3 +385,13 @@ flag {
description: "Shows summarized notifications in the UI"
bug: "390217880"
}
+
+flag {
+ name: "nm_collapsed_lines"
+ namespace: "systemui"
+ description: "Shows 2 lines for collapsed notifications by default"
+ bug: "390217880"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/app/supervision/SupervisionAppService.java b/core/java/android/app/supervision/SupervisionAppService.java
index 4530be5c270a..93eb96204444 100644
--- a/core/java/android/app/supervision/SupervisionAppService.java
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -16,7 +16,11 @@
package android.app.supervision;
+import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.Service;
+import android.app.supervision.flags.Flags;
import android.content.Intent;
import android.os.IBinder;
@@ -26,31 +30,43 @@ import android.os.IBinder;
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public class SupervisionAppService extends Service {
- private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
- @Override
- public void onEnabled() {
- SupervisionAppService.this.onEnabled();
- }
+ private final ISupervisionAppService mBinder =
+ new ISupervisionAppService.Stub() {
+ @Override
+ public void onEnabled() {
+ SupervisionAppService.this.onEnabled();
+ }
- @Override
- public void onDisabled() {
- SupervisionAppService.this.onDisabled();
- }
- };
+ @Override
+ public void onDisabled() {
+ SupervisionAppService.this.onDisabled();
+ }
+ };
+ @Nullable
@Override
- public final IBinder onBind(Intent intent) {
+ public final IBinder onBind(@Nullable Intent intent) {
return mBinder.asBinder();
}
/**
* Called when supervision is enabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onEnabled() {}
/**
* Called when supervision is disabled.
+ *
+ * @hide
*/
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_ENABLE_SUPERVISION_APP_SERVICE)
public void onDisabled() {}
}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index 615a6dffdf99..161f05bc5139 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -166,3 +166,10 @@ flag {
bug: "393517834"
is_exported: true
}
+
+flag {
+ name: "external_virtual_cameras"
+ namespace: "virtual_devices"
+ description: "Allow external virtual cameras visible only in the Context of the virtual device"
+ bug: "375609768"
+}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index b1ea6e9b68eb..219b20428d7a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2628,15 +2628,6 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
- // STOPSHIP: hack for the pre-release SDK
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- targetCode)) {
- Slog.w(TAG, "Package requires development platform " + targetCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return Build.VERSION.SDK_INT;
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + targetCode
@@ -2708,15 +2699,6 @@ public class PackageParser {
return Build.VERSION_CODES.CUR_DEVELOPMENT;
}
- // STOPSHIP: hack for the pre-release SDK
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- minCode)) {
- Slog.w(TAG, "Package requires min development platform " + minCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return Build.VERSION.SDK_INT;
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
outError[0] = "Requires development platform " + minCode
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index ded35b23608d..1ddab2c86ec2 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -104,14 +104,6 @@ public abstract class RegisteredServicesCache<V> {
private final Handler mBackgroundHandler;
- private final Runnable mClearServiceInfoCachesRunnable = new Runnable() {
- public void run() {
- synchronized (mUserIdToServiceInfoCaches) {
- mUserIdToServiceInfoCaches.clear();
- }
- }
- };
-
private static class UserServices<V> {
@GuardedBy("mServicesLock")
final Map<V, Integer> persistentServices = Maps.newHashMap();
@@ -565,9 +557,11 @@ public abstract class RegisteredServicesCache<V> {
if (Flags.optimizeParsingInRegisteredServicesCache()) {
synchronized (mUserIdToServiceInfoCaches) {
- if (mUserIdToServiceInfoCaches.numMaps() > 0) {
- mBackgroundHandler.removeCallbacks(mClearServiceInfoCachesRunnable);
- mBackgroundHandler.postDelayed(mClearServiceInfoCachesRunnable,
+ if (mUserIdToServiceInfoCaches.numElementsForKey(userId) > 0) {
+ final Integer token = Integer.valueOf(userId);
+ mBackgroundHandler.removeCallbacksAndEqualMessages(token);
+ mBackgroundHandler.postDelayed(
+ new ClearServiceInfoCachesTimeoutRunnable(userId), token,
SERVICE_INFO_CACHES_TIMEOUT_MILLIS);
}
}
@@ -953,4 +947,19 @@ public abstract class RegisteredServicesCache<V> {
return BackgroundThread.getHandler();
}
}
+
+ class ClearServiceInfoCachesTimeoutRunnable implements Runnable {
+ final int mUserId;
+
+ ClearServiceInfoCachesTimeoutRunnable(int userId) {
+ this.mUserId = userId;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mUserIdToServiceInfoCaches) {
+ mUserIdToServiceInfoCaches.delete(mUserId);
+ }
+ }
+ }
}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index c7403c0ea98c..153dd9a93490 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -316,15 +316,6 @@ public class FrameworkParsingPackageUtils {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
- // STOPSHIP: hack for the pre-release SDK
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- minCode)) {
- Slog.w(TAG, "Parsed package requires min development platform " + minCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return input.success(Build.VERSION.SDK_INT);
- }
-
// Otherwise, we're looking at an incompatible pre-release SDK.
if (platformSdkCodenames.length > 0) {
return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK,
@@ -377,27 +368,19 @@ public class FrameworkParsingPackageUtils {
return input.success(targetVers);
}
- // If it's a pre-release SDK and the codename matches this platform, it
- // definitely targets this SDK.
- if (matchTargetCode(platformSdkCodenames, targetCode)) {
- return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
- }
-
- // STOPSHIP: hack for the pre-release SDK
- if (platformSdkCodenames.length == 0
- && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals(
- targetCode)) {
- Slog.w(TAG, "Parsed package requires development platform " + targetCode
- + ", returning current version " + Build.VERSION.SDK_INT);
- return input.success(Build.VERSION.SDK_INT);
- }
-
try {
if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) {
return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
} catch (IllegalArgumentException e) {
- return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK");
+ // isAtMost() throws it when encountering an older SDK codename
+ return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, e.getMessage());
+ }
+
+ // If it's a pre-release SDK and the codename matches this platform, it
+ // definitely targets this SDK.
+ if (matchTargetCode(platformSdkCodenames, targetCode)) {
+ return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT);
}
// Otherwise, we're looking at an incompatible pre-release SDK.
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 6e9dcf5a83a1..335448bf131e 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -79,6 +79,7 @@ import android.util.Log;
import android.util.Pair;
import android.util.Size;
import android.view.Display;
+import android.window.DesktopModeFlags;
import com.android.internal.camera.flags.Flags;
import com.android.internal.util.ArrayUtils;
@@ -1685,7 +1686,7 @@ public final class CameraManager {
*/
public static int getRotationOverride(@Nullable Context context,
@Nullable PackageManager packageManager, @Nullable String packageName) {
- if (com.android.window.flags.Flags.enableCameraCompatForDesktopWindowing()) {
+ if (DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()) {
return getRotationOverrideInternal(context, packageManager, packageName);
} else {
return shouldOverrideToPortrait(packageManager, packageName)
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 1c2150f3c09f..5537135f7bfa 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -273,7 +273,7 @@ interface IInputManager {
@PermissionManuallyEnforced
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.MANAGE_KEY_GESTURES)")
- void registerKeyGestureHandler(IKeyGestureHandler handler);
+ void registerKeyGestureHandler(in int[] keyGesturesToHandle, IKeyGestureHandler handler);
@PermissionManuallyEnforced
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
diff --git a/core/java/android/hardware/input/IKeyGestureHandler.aidl b/core/java/android/hardware/input/IKeyGestureHandler.aidl
index 4da991ee85b1..08b015892710 100644
--- a/core/java/android/hardware/input/IKeyGestureHandler.aidl
+++ b/core/java/android/hardware/input/IKeyGestureHandler.aidl
@@ -20,12 +20,12 @@ import android.hardware.input.AidlKeyGestureEvent;
import android.os.IBinder;
/** @hide */
-interface IKeyGestureHandler {
+oneway interface IKeyGestureHandler {
/**
- * Called when a key gesture starts, ends, or is cancelled. If a handler returns {@code true},
- * it means they intend to handle the full gesture and should handle all the events pertaining
- * to that gesture.
+ * Called when a key gesture starts, ends, or is cancelled. It is only sent to the handler that
+ * registered the callback for that particular gesture type.
+ * {@see IInputManager#registerKeyGestureHandler(int[], IKeyGestureHandler)}
*/
- boolean handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken);
+ void handleKeyGesture(in AidlKeyGestureEvent event, in IBinder focusedToken);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index d6419afb2a5a..a66ac76d7597 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1446,16 +1446,18 @@ public final class InputManager {
/**
* Registers a key gesture event handler for {@link KeyGestureEvent} handling.
*
+ * @param keyGesturesToHandle list of KeyGestureTypes to listen to
* @param handler the {@link KeyGestureEventHandler}
- * @throws IllegalArgumentException if {@code handler} has already been registered previously.
+ * @throws IllegalArgumentException if {@code handler} has already been registered previously
+ * or key gestures provided are already registered by some other gesture handler.
* @throws NullPointerException if {@code handler} or {@code executor} is null.
* @hide
* @see #unregisterKeyGestureEventHandler(KeyGestureEventHandler)
*/
@RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
- public void registerKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler)
- throws IllegalArgumentException {
- mGlobal.registerKeyGestureEventHandler(handler);
+ public void registerKeyGestureEventHandler(List<Integer> keyGesturesToHandle,
+ @NonNull KeyGestureEventHandler handler) throws IllegalArgumentException {
+ mGlobal.registerKeyGestureEventHandler(keyGesturesToHandle, handler);
}
/**
@@ -1463,7 +1465,7 @@ public final class InputManager {
*
* @param handler the {@link KeyGestureEventHandler}
* @hide
- * @see #registerKeyGestureEventHandler(KeyGestureEventHandler)
+ * @see #registerKeyGestureEventHandler(List, KeyGestureEventHandler)
*/
@RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
public void unregisterKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) {
@@ -1741,7 +1743,7 @@ public final class InputManager {
* {@see KeyGestureEventListener} which is to listen to successfully handled key gestures, this
* interface allows system components to register handler for handling key gestures.
*
- * @see #registerKeyGestureEventHandler(KeyGestureEventHandler)
+ * @see #registerKeyGestureEventHandler(List, KeyGestureEventHandler)
* @see #unregisterKeyGestureEventHandler(KeyGestureEventHandler)
*
* <p> NOTE: All callbacks will occur on system main and input threads, so the caller needs
@@ -1750,14 +1752,11 @@ public final class InputManager {
*/
public interface KeyGestureEventHandler {
/**
- * Called when a key gesture event starts, is completed, or is cancelled. If a handler
- * returns {@code true}, it implies that the handler intends to handle the key gesture and
- * only this handler will receive the future events for this key gesture.
+ * Called when a key gesture event starts, is completed, or is cancelled.
*
* @param event the gesture event
*/
- boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
- @Nullable IBinder focusedToken);
+ void handleKeyGestureEvent(@NonNull KeyGestureEvent event, @Nullable IBinder focusedToken);
}
/** @hide */
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index c4b4831ba76e..754182ce3d11 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -25,8 +25,8 @@ import android.hardware.BatteryState;
import android.hardware.SensorManager;
import android.hardware.input.InputManager.InputDeviceBatteryListener;
import android.hardware.input.InputManager.InputDeviceListener;
-import android.hardware.input.InputManager.KeyGestureEventHandler;
import android.hardware.input.InputManager.KeyEventActivityListener;
+import android.hardware.input.InputManager.KeyGestureEventHandler;
import android.hardware.input.InputManager.KeyGestureEventListener;
import android.hardware.input.InputManager.KeyboardBacklightListener;
import android.hardware.input.InputManager.OnTabletModeChangedListener;
@@ -49,6 +49,7 @@ import android.os.ServiceManager;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.VibratorManager;
+import android.util.IntArray;
import android.util.Log;
import android.util.SparseArray;
import android.view.Display;
@@ -132,13 +133,13 @@ public final class InputManagerGlobal {
@Nullable
private IKeyEventActivityListener mKeyEventActivityListener;
- private final Object mKeyGestureEventHandlerLock = new Object();
- @GuardedBy("mKeyGestureEventHandlerLock")
- @Nullable
- private ArrayList<KeyGestureEventHandler> mKeyGestureEventHandlers;
- @GuardedBy("mKeyGestureEventHandlerLock")
+ @GuardedBy("mKeyGesturesToHandlerMap")
@Nullable
private IKeyGestureHandler mKeyGestureHandler;
+ @GuardedBy("mKeyGesturesToHandlerMap")
+ private final SparseArray<KeyGestureEventHandler> mKeyGesturesToHandlerMap =
+ new SparseArray<>();
+
// InputDeviceSensorManager gets notified synchronously from the binder thread when input
// devices change, so it must be synchronized with the input device listeners.
@@ -1177,50 +1178,69 @@ public final class InputManagerGlobal {
private class LocalKeyGestureHandler extends IKeyGestureHandler.Stub {
@Override
- public boolean handleKeyGesture(@NonNull AidlKeyGestureEvent ev, IBinder focusedToken) {
- synchronized (mKeyGestureEventHandlerLock) {
- if (mKeyGestureEventHandlers == null) {
- return false;
- }
- final int numHandlers = mKeyGestureEventHandlers.size();
- final KeyGestureEvent event = new KeyGestureEvent(ev);
- for (int i = 0; i < numHandlers; i++) {
- KeyGestureEventHandler handler = mKeyGestureEventHandlers.get(i);
- if (handler.handleKeyGestureEvent(event, focusedToken)) {
- return true;
- }
+ public void handleKeyGesture(@NonNull AidlKeyGestureEvent ev, IBinder focusedToken) {
+ synchronized (mKeyGesturesToHandlerMap) {
+ KeyGestureEventHandler handler = mKeyGesturesToHandlerMap.get(ev.gestureType);
+ if (handler == null) {
+ Log.w(TAG, "Key gesture event " + ev.gestureType
+ + " occurred without a registered handler!");
+ return;
}
+ handler.handleKeyGestureEvent(new KeyGestureEvent(ev), focusedToken);
}
- return false;
}
}
/**
- * @see InputManager#registerKeyGestureEventHandler(KeyGestureEventHandler)
+ * @see InputManager#registerKeyGestureEventHandler(List, KeyGestureEventHandler)
*/
@RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
- void registerKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler)
- throws IllegalArgumentException {
+ void registerKeyGestureEventHandler(List<Integer> keyGesturesToHandle,
+ @NonNull KeyGestureEventHandler handler) throws IllegalArgumentException {
+ Objects.requireNonNull(keyGesturesToHandle, "List of gestures should not be null");
Objects.requireNonNull(handler, "handler should not be null");
- synchronized (mKeyGestureEventHandlerLock) {
- if (mKeyGestureHandler == null) {
- mKeyGestureEventHandlers = new ArrayList<>();
- mKeyGestureHandler = new LocalKeyGestureHandler();
+ if (keyGesturesToHandle.isEmpty()) {
+ throw new IllegalArgumentException("No key gestures provided!");
+ }
- try {
- mIm.registerKeyGestureHandler(mKeyGestureHandler);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
+ synchronized (mKeyGesturesToHandlerMap) {
+ IntArray newKeyGestures = new IntArray(
+ keyGesturesToHandle.size() + mKeyGesturesToHandlerMap.size());
+
+ // Check if the handler already exists
+ for (int i = 0; i < mKeyGesturesToHandlerMap.size(); i++) {
+ KeyGestureEventHandler h = mKeyGesturesToHandlerMap.valueAt(i);
+ if (h == handler) {
+ throw new IllegalArgumentException("Handler has already been registered!");
}
+ newKeyGestures.add(mKeyGesturesToHandlerMap.keyAt(i));
}
- final int numHandlers = mKeyGestureEventHandlers.size();
- for (int i = 0; i < numHandlers; i++) {
- if (mKeyGestureEventHandlers.get(i) == handler) {
- throw new IllegalArgumentException("Handler has already been registered!");
+
+ // Check if any of the key gestures are already handled by existing handlers
+ for (int gesture : keyGesturesToHandle) {
+ if (mKeyGesturesToHandlerMap.contains(gesture)) {
+ throw new IllegalArgumentException("Key gesture " + gesture
+ + " is already registered by another handler!");
+ }
+ newKeyGestures.add(gesture);
+ }
+
+ try {
+ // If handler was already registered for this process, we need to unregister and
+ // re-register it for the new set of gestures
+ if (mKeyGestureHandler != null) {
+ mIm.unregisterKeyGestureHandler(mKeyGestureHandler);
+ } else {
+ mKeyGestureHandler = new LocalKeyGestureHandler();
+ }
+ mIm.registerKeyGestureHandler(newKeyGestures.toArray(), mKeyGestureHandler);
+ for (int gesture : keyGesturesToHandle) {
+ mKeyGesturesToHandlerMap.put(gesture, handler);
}
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
- mKeyGestureEventHandlers.add(handler);
}
}
@@ -1231,18 +1251,21 @@ public final class InputManagerGlobal {
void unregisterKeyGestureEventHandler(@NonNull KeyGestureEventHandler handler) {
Objects.requireNonNull(handler, "handler should not be null");
- synchronized (mKeyGestureEventHandlerLock) {
- if (mKeyGestureEventHandlers == null) {
+ synchronized (mKeyGesturesToHandlerMap) {
+ if (mKeyGestureHandler == null) {
return;
}
- mKeyGestureEventHandlers.removeIf(existingHandler -> existingHandler == handler);
- if (mKeyGestureEventHandlers.isEmpty()) {
+ for (int i = mKeyGesturesToHandlerMap.size() - 1; i >= 0; i--) {
+ if (mKeyGesturesToHandlerMap.valueAt(i) == handler) {
+ mKeyGesturesToHandlerMap.removeAt(i);
+ }
+ }
+ if (mKeyGesturesToHandlerMap.size() == 0) {
try {
mIm.unregisterKeyGestureHandler(mKeyGestureHandler);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
- mKeyGestureEventHandlers = null;
mKeyGestureHandler = null;
}
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 6c2ce3685b30..c41a5ce02e61 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -213,3 +213,12 @@ flag {
is_fixed_read_only: true
}
+flag {
+ name: "fix_search_modifier_fallbacks"
+ namespace: "input"
+ description: "Fixes a bug in which fallbacks from Search based key combinations were not activating."
+ bug: "384113980"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/hardware/serial/flags/flags.aconfig b/core/java/android/hardware/serial/flags/flags.aconfig
index d8244ba55fcc..bdb8b40af2bf 100644
--- a/core/java/android/hardware/serial/flags/flags.aconfig
+++ b/core/java/android/hardware/serial/flags/flags.aconfig
@@ -1,4 +1,4 @@
-package: "android.hardware.serial"
+package: "android.hardware.serial.flags"
container: "system"
flag {
diff --git a/core/java/android/net/VpnManager.java b/core/java/android/net/VpnManager.java
index c50bc569de72..4ef293a90a80 100644
--- a/core/java/android/net/VpnManager.java
+++ b/core/java/android/net/VpnManager.java
@@ -20,6 +20,7 @@ import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static com.android.internal.util.Preconditions.checkNotNull;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -32,6 +33,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
+import android.net.platform.flags.Flags;
import android.os.RemoteException;
import com.android.internal.net.LegacyVpnInfo;
@@ -85,13 +87,33 @@ public class VpnManager {
public static final int TYPE_VPN_LEGACY = 3;
/**
- * An VPN created by OEM code through other means than {@link VpnService} or {@link VpnManager}.
+ * A VPN created by OEM code through other means than {@link VpnService} or {@link VpnManager}.
* @hide
*/
@SystemApi(client = MODULE_LIBRARIES)
public static final int TYPE_VPN_OEM = 4;
/**
+ * A VPN created by OEM code using {@link VpnService}, and which OEM code desires to
+ * differentiate from other VPN types. The core networking stack will treat this VPN type
+ * similarly to {@link #TYPE_VPN_SERVICE}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VPN_TYPE_OEM_SERVICE_AND_LEGACY)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int TYPE_VPN_OEM_SERVICE = 5;
+
+ /**
+ * A VPN created by OEM code using the legacy VPN mechanisms, and which OEM code desires to
+ * differentiate from other VPN types. The core networking stack will treat this VPN type
+ * similarly to {@link #TYPE_VPN_LEGACY}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_VPN_TYPE_OEM_SERVICE_AND_LEGACY)
+ @SystemApi(client = MODULE_LIBRARIES)
+ public static final int TYPE_VPN_OEM_LEGACY = 6;
+
+ /**
* Channel for VPN notifications.
* @hide
*/
@@ -308,7 +330,7 @@ public class VpnManager {
/** @hide */
@IntDef(value = {TYPE_VPN_NONE, TYPE_VPN_SERVICE, TYPE_VPN_PLATFORM, TYPE_VPN_LEGACY,
- TYPE_VPN_OEM})
+ TYPE_VPN_OEM, TYPE_VPN_OEM_SERVICE, TYPE_VPN_OEM_LEGACY})
@Retention(RetentionPolicy.SOURCE)
public @interface VpnType {}
diff --git a/core/java/android/net/flags.aconfig b/core/java/android/net/flags.aconfig
index 8d12b76e23ff..519729bc1c88 100644
--- a/core/java/android/net/flags.aconfig
+++ b/core/java/android/net/flags.aconfig
@@ -37,3 +37,11 @@ flag {
description: "Flag for MDNS quality, reliability and performance improvement in 25Q2"
bug: "373270045"
}
+
+flag {
+ name: "vpn_type_oem_service_and_legacy"
+ namespace: "android_core_networking"
+ is_exported: false
+ description: "Flags the TYPE_VPN_OEM_SERVICE and TYPE_VPN_OEM_LEGACY VpnManager API constants"
+ bug: "389829981"
+}
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index c21959b16fbb..c3c28b20d649 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -34,8 +34,6 @@ import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.annotation.optimization.NeverCompile;
import java.io.FileDescriptor;
@@ -1121,7 +1119,6 @@ public final class MessageQueue {
msg.markInUse();
msg.arg1 = token;
- incAndTraceMessageCount(msg, when);
if (!enqueueMessageUnchecked(msg, when)) {
Log.wtf(TAG_C, "Unexpected error while adding sync barrier!");
@@ -1137,7 +1134,6 @@ public final class MessageQueue {
msg.markInUse();
msg.when = when;
msg.arg1 = token;
- incAndTraceMessageCount(msg, when);
if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
/* Message goes to tail of list */
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 932836f8a050..8b96b89c949a 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -232,7 +232,8 @@ public final class PerfettoTrace {
* @param eventName The event name to appear in the trace.
*/
public static PerfettoTrackEventExtra.Builder instant(Category category, String eventName) {
- return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_INSTANT, category)
+ return PerfettoTrackEventExtra.builder(category.isEnabled())
+ .init(PERFETTO_TE_TYPE_INSTANT, category)
.setEventName(eventName);
}
@@ -243,7 +244,8 @@ public final class PerfettoTrace {
* @param eventName The event name to appear in the trace.
*/
public static PerfettoTrackEventExtra.Builder begin(Category category, String eventName) {
- return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
+ return PerfettoTrackEventExtra.builder(category.isEnabled())
+ .init(PERFETTO_TE_TYPE_SLICE_BEGIN, category)
.setEventName(eventName);
}
@@ -253,7 +255,8 @@ public final class PerfettoTrace {
* @param category The perfetto category.
*/
public static PerfettoTrackEventExtra.Builder end(Category category) {
- return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_SLICE_END, category);
+ return PerfettoTrackEventExtra.builder(category.isEnabled())
+ .init(PERFETTO_TE_TYPE_SLICE_END, category);
}
/**
@@ -263,7 +266,8 @@ public final class PerfettoTrace {
* @param value The value of the counter.
*/
public static PerfettoTrackEventExtra.Builder counter(Category category, long value) {
- return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ return PerfettoTrackEventExtra.builder(category.isEnabled())
+ .init(PERFETTO_TE_TYPE_COUNTER, category)
.setCounter(value);
}
@@ -286,7 +290,8 @@ public final class PerfettoTrace {
* @param value The value of the counter.
*/
public static PerfettoTrackEventExtra.Builder counter(Category category, double value) {
- return PerfettoTrackEventExtra.builder().init(PERFETTO_TE_TYPE_COUNTER, category)
+ return PerfettoTrackEventExtra.builder(category.isEnabled())
+ .init(PERFETTO_TE_TYPE_COUNTER, category)
.setCounter(value);
}
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index 07b44a87ef88..5fc7cf7be246 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -37,6 +37,7 @@ import java.util.function.Supplier;
public final class PerfettoTrackEventExtra {
private static final boolean DEBUG = false;
private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
+ private static final Builder NO_OP_BUILDER = new Builder(/* extra= */ null, /* isCategoryEnabled= */ false);
private static final ThreadLocal<PerfettoTrackEventExtra> sTrackEventExtra =
new ThreadLocal<PerfettoTrackEventExtra>() {
@Override
@@ -153,8 +154,8 @@ public final class PerfettoTrackEventExtra {
private Builder mParent;
private FieldContainer mCurrentContainer;
- private boolean mIsCategoryEnabled;
+ private final boolean mIsCategoryEnabled;
private final CounterInt64 mCounterInt64;
private final CounterDouble mCounterDouble;
private final Proto mProto;
@@ -175,24 +176,29 @@ public final class PerfettoTrackEventExtra {
private final Pool<Builder> mBuilderCache;
private Builder() {
- mExtra = sTrackEventExtra.get();
- mNamedTrackCache = mExtra.mNamedTrackCache;
- mCounterTrackCache = mExtra.mCounterTrackCache;
- mArgInt64Cache = mExtra.mArgInt64Cache;
- mArgDoubleCache = mExtra.mArgDoubleCache;
- mArgBoolCache = mExtra.mArgBoolCache;
- mArgStringCache = mExtra.mArgStringCache;
- mFieldInt64Cache = mExtra.mFieldInt64Cache;
- mFieldDoubleCache = mExtra.mFieldDoubleCache;
- mFieldStringCache = mExtra.mFieldStringCache;
- mFieldNestedCache = mExtra.mFieldNestedCache;
- mBuilderCache = mExtra.mBuilderCache;
-
- mCounterInt64 = mExtra.getCounterInt64();
- mCounterDouble = mExtra.getCounterDouble();
- mProto = mExtra.getProto();
- mFlow = mExtra.getFlow();
- mTerminatingFlow = mExtra.getTerminatingFlow();
+ this(sTrackEventExtra.get(), true);
+ }
+
+ public Builder(PerfettoTrackEventExtra extra, boolean isCategoryEnabled) {
+ mIsCategoryEnabled = isCategoryEnabled;
+ mExtra = extra;
+ mNamedTrackCache = mExtra == null ? null : mExtra.mNamedTrackCache;
+ mCounterTrackCache = mExtra == null ? null : mExtra.mCounterTrackCache;
+ mArgInt64Cache = mExtra == null ? null : mExtra.mArgInt64Cache;
+ mArgDoubleCache = mExtra == null ? null : mExtra.mArgDoubleCache;
+ mArgBoolCache = mExtra == null ? null : mExtra.mArgBoolCache;
+ mArgStringCache = mExtra == null ? null : mExtra.mArgStringCache;
+ mFieldInt64Cache = mExtra == null ? null : mExtra.mFieldInt64Cache;
+ mFieldDoubleCache = mExtra == null ? null : mExtra.mFieldDoubleCache;
+ mFieldStringCache = mExtra == null ? null : mExtra.mFieldStringCache;
+ mFieldNestedCache = mExtra == null ? null : mExtra.mFieldNestedCache;
+ mBuilderCache = mExtra == null ? null : mExtra.mBuilderCache;
+
+ mCounterInt64 = mExtra == null ? null : mExtra.getCounterInt64();
+ mCounterDouble = mExtra == null ? null : mExtra.getCounterDouble();
+ mProto = mExtra == null ? null : mExtra.getProto();
+ mFlow = mExtra == null ? null : mExtra.getFlow();
+ mTerminatingFlow = mExtra == null ? null : mExtra.getTerminatingFlow();
}
/**
@@ -214,6 +220,10 @@ public final class PerfettoTrackEventExtra {
* Initialize the builder for a new trace event.
*/
public Builder init(int traceType, PerfettoTrace.Category category) {
+ if (!mIsCategoryEnabled) {
+ return this;
+ }
+
mTraceType = traceType;
mCategory = category;
mEventName = "";
@@ -225,7 +235,7 @@ public final class PerfettoTrackEventExtra {
mExtra.reset();
// Reset after on init in case the thread created builders without calling emit
- return initInternal(this, null, category.isEnabled());
+ return initInternal(this, null);
}
/**
@@ -529,7 +539,7 @@ public final class PerfettoTrackEventExtra {
}
mProto.clearFields();
mExtra.addPerfettoPointer(mProto);
- return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto, true);
+ return mBuilderCache.get(sBuilderSupplier).initInternal(this, mProto);
}
/**
@@ -560,7 +570,7 @@ public final class PerfettoTrackEventExtra {
FieldNested field = mFieldNestedCache.get(sFieldNestedSupplier);
field.setId(id);
mExtra.addPerfettoPointer(mCurrentContainer, field);
- return mBuilderCache.get(sBuilderSupplier).initInternal(this, field, true);
+ return mBuilderCache.get(sBuilderSupplier).initInternal(this, field);
}
/**
@@ -577,11 +587,9 @@ public final class PerfettoTrackEventExtra {
}
- private Builder initInternal(Builder parent, FieldContainer field,
- boolean isCategoryEnabled) {
+ private Builder initInternal(Builder parent, FieldContainer field) {
mParent = parent;
mCurrentContainer = field;
- mIsCategoryEnabled = isCategoryEnabled;
mIsBuilt = false;
return this;
@@ -613,9 +621,12 @@ public final class PerfettoTrackEventExtra {
/**
* Start a {@link Builder} to build a {@link PerfettoTrackEventExtra}.
*/
- public static Builder builder() {
- return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier).initInternal(null, null,
- false);
+ public static Builder builder(boolean isCategoryEnabled) {
+ if (isCategoryEnabled) {
+ return sTrackEventExtra.get().mBuilderCache.get(sBuilderSupplier)
+ .initInternal(null, null);
+ }
+ return NO_OP_BUILDER;
}
private final RingBuffer<NamedTrack> mNamedTrackCache =
diff --git a/core/java/android/os/image/flags/trade_in_mode_flags.aconfig b/core/java/android/os/image/flags/trade_in_mode_flags.aconfig
index e2e56ef70d62..c1adfe50db9e 100644
--- a/core/java/android/os/image/flags/trade_in_mode_flags.aconfig
+++ b/core/java/android/os/image/flags/trade_in_mode_flags.aconfig
@@ -9,3 +9,12 @@ flag {
bug: "332683751"
is_fixed_read_only: true
}
+
+flag {
+ name: "trade_in_mode_2025q4"
+ is_exported: true
+ namespace: "phoenix"
+ description: "Enable Trade-in Mode 2025Q4 changes"
+ bug: "397154502"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b7e296228e45..e7bca1419418 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13317,10 +13317,18 @@ public final class Settings {
public static final String CONTEXTUAL_SEARCH_PACKAGE = "contextual_search_package";
/**
- * Inetger property which determines whether advanced protection is on or not.
+ * Integer property which determines whether advanced protection is on or not.
* @hide
*/
public static final String ADVANCED_PROTECTION_MODE = "advanced_protection_mode";
+
+ /**
+ * Integer property which determines whether advanced protection USB data protection
+ * feature is on or not.
+ *
+ * @hide
+ */
+ public static final String AAPM_USB_DATA_PROTECTION = "aapm_usb_data_protection";
}
/**
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 7ee0ff15c5ad..c59907937d6a 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -129,7 +129,7 @@ public final class ArrayMap<K, V> implements Map<K, V> {
return ContainerHelpers.binarySearch(hashes, N, hash);
} catch (ArrayIndexOutOfBoundsException e) {
if (CONCURRENT_MODIFICATION_EXCEPTIONS) {
- throw new ConcurrentModificationException();
+ throw new ConcurrentModificationException(e);
} else {
throw e; // the cache is poisoned at this point, there's not much we can do
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e9dfe6d972a..4c578fb93600 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -2039,8 +2039,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
} else if (Flags.refactorInsetsController()) {
if ((typesToReport & ime()) != 0 && mImeSourceConsumer != null) {
InsetsSourceControl control = mImeSourceConsumer.getControl();
- if (control != null && control.getLeash() == null) {
- // If the IME was requested twice, and we didn't receive the controls
+ if (control == null || control.getLeash() == null) {
+ // If the IME was requested to show twice, and we didn't receive the controls
// yet, this request will not continue. It should be cancelled here, as
// it would time out otherwise.
ImeTracker.forLogging().onCancelled(statsToken,
diff --git a/core/java/android/view/OrientationEventListener.java b/core/java/android/view/OrientationEventListener.java
index 2feb44ffc3ae..892b80bd0213 100644
--- a/core/java/android/view/OrientationEventListener.java
+++ b/core/java/android/view/OrientationEventListener.java
@@ -26,8 +26,7 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Log;
-
-import com.android.window.flags.Flags;
+import android.window.DesktopModeFlags;
/**
* Helper class for receiving notifications from the SensorManager when
@@ -77,9 +76,10 @@ public abstract class OrientationEventListener {
mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if (mSensor != null) {
// Create listener only if sensors do exist.
- mSensorEventListener = Flags.enableCameraCompatForDesktopWindowing()
- ? new CompatSensorEventListenerImpl(new SensorEventListenerImpl())
- : new SensorEventListenerImpl();
+ mSensorEventListener =
+ DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()
+ ? new CompatSensorEventListenerImpl(new SensorEventListenerImpl())
+ : new SensorEventListenerImpl();
}
}
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 97cf8fc748e8..8944c3fa0b19 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -120,7 +120,7 @@ public class WindowManagerImpl implements WindowManager {
this(context, null /* parentWindow */, null /* clientToken */);
}
- private WindowManagerImpl(Context context, Window parentWindow,
+ public WindowManagerImpl(Context context, Window parentWindow,
@Nullable IBinder windowContextToken) {
mContext = context;
mParentWindow = parentWindow;
diff --git a/core/java/android/widget/DatePickerCalendarDelegate.java b/core/java/android/widget/DatePickerCalendarDelegate.java
index 536b81f77174..4f7eef007a68 100644
--- a/core/java/android/widget/DatePickerCalendarDelegate.java
+++ b/core/java/android/widget/DatePickerCalendarDelegate.java
@@ -34,6 +34,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.DayPickerView.OnDaySelectedListener;
import android.widget.YearPickerView.OnYearSelectedListener;
@@ -76,10 +77,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
private DayPickerView mDayPickerView;
private YearPickerView mYearPickerView;
- // Accessibility strings.
- private String mSelectDay;
- private String mSelectYear;
-
private int mCurrentView = UNINITIALIZED;
private final Calendar mTempDate;
@@ -118,8 +115,15 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
final ViewGroup header = mContainer.findViewById(R.id.date_picker_header);
mHeaderYear = header.findViewById(R.id.date_picker_header_year);
mHeaderYear.setOnClickListener(mOnHeaderClickListener);
+ mHeaderYear.setAccessibilityDelegate(
+ new ClickActionDelegate(context, R.string.select_year));
+ mHeaderYear.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+
mHeaderMonthDay = header.findViewById(R.id.date_picker_header_date);
mHeaderMonthDay.setOnClickListener(mOnHeaderClickListener);
+ mHeaderMonthDay.setAccessibilityDelegate(
+ new ClickActionDelegate(context, R.string.select_day));
+ mHeaderMonthDay.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
// For the sake of backwards compatibility, attempt to extract the text
// color from the header month text appearance. If it's set, we'll let
@@ -170,10 +174,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mYearPickerView.setYear(mCurrentDate.get(Calendar.YEAR));
mYearPickerView.setOnYearSelectedListener(mOnYearSelectedListener);
- // Set up content descriptions.
- mSelectDay = res.getString(R.string.select_day);
- mSelectYear = res.getString(R.string.select_year);
-
// Initialize for current locale. This also initializes the date, so no
// need to call onDateChanged.
onLocaleChanged(mCurrentLocale);
@@ -230,6 +230,22 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
return srcRgb | (dstAlpha << 24);
}
+ private static class ClickActionDelegate extends View.AccessibilityDelegate {
+ private final AccessibilityNodeInfo.AccessibilityAction mClickAction;
+
+ ClickActionDelegate(Context context, int resId) {
+ mClickAction = new AccessibilityNodeInfo.AccessibilityAction(
+ AccessibilityNodeInfo.ACTION_CLICK, context.getString(resId));
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ info.addAction(mClickAction);
+ }
+ }
+
/**
* Listener called when the user selects a day in the day picker view.
*/
@@ -310,10 +326,10 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mYearFormat = DateFormat.getInstanceForSkeleton("y", locale);
// Update the header text.
- onCurrentDateChanged(false);
+ onCurrentDateChanged();
}
- private void onCurrentDateChanged(boolean announce) {
+ private void onCurrentDateChanged() {
if (mHeaderYear == null) {
// Abort, we haven't initialized yet. This method will get called
// again later after everything has been set up.
@@ -325,11 +341,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
final String monthDay = mMonthDayFormat.format(mCurrentDate.getTime());
mHeaderMonthDay.setText(monthDay);
-
- // TODO: This should use live regions.
- if (announce) {
- mAnimator.announceForAccessibility(getFormattedCurrentDate());
- }
}
private void setCurrentView(final int viewIndex) {
@@ -343,8 +354,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mAnimator.setDisplayedChild(VIEW_MONTH_DAY);
mCurrentView = viewIndex;
}
-
- mAnimator.announceForAccessibility(mSelectDay);
break;
case VIEW_YEAR:
final int year = mCurrentDate.get(Calendar.YEAR);
@@ -364,7 +373,6 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mCurrentView = viewIndex;
}
- mAnimator.announceForAccessibility(mSelectYear);
break;
}
}
@@ -409,7 +417,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mDayPickerView.setDate(mCurrentDate.getTimeInMillis());
mYearPickerView.setYear(year);
- onCurrentDateChanged(fromUser);
+ onCurrentDateChanged();
if (fromUser) {
tryVibrate();
@@ -564,7 +572,7 @@ class DatePickerCalendarDelegate extends DatePicker.AbstractDatePickerDelegate {
mMinDate.setTimeInMillis(ss.getMinDate());
mMaxDate.setTimeInMillis(ss.getMaxDate());
- onCurrentDateChanged(false);
+ onCurrentDateChanged();
final int currentView = ss.getCurrentView();
setCurrentView(currentView);
diff --git a/core/java/android/widget/TimePickerClockDelegate.java b/core/java/android/widget/TimePickerClockDelegate.java
index a453c2818566..ebb0f99e988b 100644
--- a/core/java/android/widget/TimePickerClockDelegate.java
+++ b/core/java/android/widget/TimePickerClockDelegate.java
@@ -121,12 +121,8 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
// Localization data.
private boolean mHourFormatShowLeadingZero;
- private boolean mHourFormatStartsAtZero;
-
- // Most recent time announcement values for accessibility.
- private CharSequence mLastAnnouncedText;
- private boolean mLastAnnouncedIsHour;
+ private boolean mHourFormatStartsAtZero;
public TimePickerClockDelegate(TimePicker delegator, Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
super(delegator, context);
@@ -155,6 +151,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
mHourView.setOnDigitEnteredListener(mDigitEnteredListener);
mHourView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_hours));
+ mHourView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
mSeparatorView = (TextView) mainView.findViewById(R.id.separator);
mMinuteView = (NumericTextView) mainView.findViewById(R.id.minutes);
mMinuteView.setOnClickListener(mClickListener);
@@ -162,6 +159,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
mMinuteView.setOnDigitEnteredListener(mDigitEnteredListener);
mMinuteView.setAccessibilityDelegate(
new ClickActionDelegate(context, R.string.select_minutes));
+ mMinuteView.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
mMinuteView.setRange(0, 59);
// Set up AM/PM labels.
@@ -435,7 +433,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
private void updateRadialPicker(int index) {
mRadialTimePickerView.initialize(mCurrentHour, mCurrentMinute, mIs24Hour);
- setCurrentItemShowing(index, false, true);
+ setCurrentItemShowing(index, false);
}
private void updateHeaderAmPm() {
@@ -786,18 +784,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
private void updateHeaderHour(int hourOfDay, boolean announce) {
final int localizedHour = getLocalizedHour(hourOfDay);
mHourView.setValue(localizedHour);
-
- if (announce) {
- tryAnnounceForAccessibility(mHourView.getText(), true);
- }
}
private void updateHeaderMinute(int minuteOfHour, boolean announce) {
mMinuteView.setValue(minuteOfHour);
-
- if (announce) {
- tryAnnounceForAccessibility(mMinuteView.getText(), false);
- }
}
/**
@@ -876,31 +866,12 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
return -1;
}
- private void tryAnnounceForAccessibility(CharSequence text, boolean isHour) {
- if (mLastAnnouncedIsHour != isHour || !text.equals(mLastAnnouncedText)) {
- // TODO: Find a better solution, potentially live regions?
- mDelegator.announceForAccessibility(text);
- mLastAnnouncedText = text;
- mLastAnnouncedIsHour = isHour;
- }
- }
-
/**
* Show either Hours or Minutes.
*/
- private void setCurrentItemShowing(int index, boolean animateCircle, boolean announce) {
+ private void setCurrentItemShowing(int index, boolean animateCircle) {
mRadialTimePickerView.setCurrentItemShowing(index, animateCircle);
- if (index == HOUR_INDEX) {
- if (announce) {
- mDelegator.announceForAccessibility(mSelectHours);
- }
- } else {
- if (announce) {
- mDelegator.announceForAccessibility(mSelectMinutes);
- }
- }
-
mHourView.setActivated(index == HOUR_INDEX);
mMinuteView.setActivated(index == MINUTE_INDEX);
}
@@ -930,10 +901,7 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
final boolean isTransition = mAllowAutoAdvance && autoAdvance;
setHourInternal(newValue, FROM_RADIAL_PICKER, !isTransition, true);
if (isTransition) {
- setCurrentItemShowing(MINUTE_INDEX, true, false);
-
- final int localizedHour = getLocalizedHour(newValue);
- mDelegator.announceForAccessibility(localizedHour + ". " + mSelectMinutes);
+ setCurrentItemShowing(MINUTE_INDEX, true);
}
break;
case RadialTimePickerView.MINUTES:
@@ -1030,10 +998,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
setAmOrPm(PM);
break;
case R.id.hours:
- setCurrentItemShowing(HOUR_INDEX, true, true);
+ setCurrentItemShowing(HOUR_INDEX, true);
break;
case R.id.minutes:
- setCurrentItemShowing(MINUTE_INDEX, true, true);
+ setCurrentItemShowing(MINUTE_INDEX, true);
break;
default:
// Failed to handle this click, don't vibrate.
@@ -1058,10 +1026,10 @@ class TimePickerClockDelegate extends TimePicker.AbstractTimePickerDelegate {
setAmOrPm(PM);
break;
case R.id.hours:
- setCurrentItemShowing(HOUR_INDEX, true, true);
+ setCurrentItemShowing(HOUR_INDEX, true);
break;
case R.id.minutes:
- setCurrentItemShowing(MINUTE_INDEX, true, true);
+ setCurrentItemShowing(MINUTE_INDEX, true);
break;
default:
// Failed to handle this click, don't vibrate.
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 20792c91393f..d5120ffb4de6 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -44,10 +44,12 @@ public enum DesktopModeFlags {
// All desktop mode related flags to be overridden by developer option toggle will be added here
// go/keep-sorted start
DISABLE_DESKTOP_LAUNCH_PARAMS_OUTSIDE_DESKTOP_BUG_FIX(
- Flags::disableDesktopLaunchParamsOutsideDesktopBugFix, false),
+ Flags::disableDesktopLaunchParamsOutsideDesktopBugFix, true),
DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
ENABLE_ACCESSIBLE_CUSTOM_HEADERS(Flags::enableAccessibleCustomHeaders, true),
ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
+ ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION(
+ Flags::enableCameraCompatForDesktopWindowing, true),
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(Flags::enableCaptionCompatInsetForceConsumption,
true),
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
@@ -83,7 +85,7 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingEnterTransitionBugfix, true),
ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX(
- Flags::enableDesktopWindowingExitByMinimizeTransitionBugfix, false),
+ Flags::enableDesktopWindowingExitByMinimizeTransitionBugfix, true),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingExitTransitionsBugfix, true),
ENABLE_DESKTOP_WINDOWING_HSUM(Flags::enableDesktopWindowingHsum, true),
@@ -103,7 +105,7 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity,
true),
- ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, false),
+ ENABLE_DRAG_RESIZE_SET_UP_IN_BG_THREAD(Flags::enableDragResizeSetUpInBgThread, true),
ENABLE_DRAG_TO_DESKTOP_INCOMING_TRANSITIONS_BUGFIX(
Flags::enableDragToDesktopIncomingTransitionsBugfix, false),
ENABLE_FULLY_IMMERSIVE_IN_DESKTOP(Flags::enableFullyImmersiveInDesktop, true),
@@ -120,7 +122,7 @@ public enum DesktopModeFlags {
ENABLE_RESTORE_TO_PREVIOUS_SIZE_FROM_DESKTOP_IMMERSIVE(
Flags::enableRestoreToPreviousSizeFromDesktopImmersive, true),
ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX(
- Flags::enableShellInitialBoundsRegressionBugFix, false),
+ Flags::enableShellInitialBoundsRegressionBugFix, true),
ENABLE_START_LAUNCH_TRANSITION_FROM_TASKBAR_BUGFIX(
Flags::enableStartLaunchTransitionFromTaskbarBugfix, true),
ENABLE_TASKBAR_OVERFLOW(Flags::enableTaskbarOverflow, false),
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index 648335c5ae73..0d87b73a5e03 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -996,3 +996,13 @@ flag {
description: "Enables the home to be shown behind the desktop."
bug: "375644149"
}
+
+flag {
+ name: "enable_desktop_ime_bugfix"
+ namespace: "lse_desktop_experience"
+ description: "Enables bugfix to handle IME interactions in desktop windowing."
+ bug: "388570293"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/jank/DisplayResolutionTracker.java b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
index 5d66b3c10197..74153157df4d 100644
--- a/core/java/com/android/internal/jank/DisplayResolutionTracker.java
+++ b/core/java/com/android/internal/jank/DisplayResolutionTracker.java
@@ -142,14 +142,23 @@ public class DisplayResolutionTracker {
public interface DisplayInterface {
/** Reurns an implementation wrapping {@link DisplayManagerGlobal}. */
static DisplayInterface getDefault(@Nullable Handler handler) {
+ long displayEventsToBeSubscribed;
+ if (com.android.server.display.feature.flags.Flags
+ .displayListenerPerformanceImprovements()
+ && com.android.server.display.feature.flags.Flags
+ .delayImplicitRrRegistrationUntilRrAccessed()) {
+ displayEventsToBeSubscribed = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
+ } else {
+ displayEventsToBeSubscribed = DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
DisplayManagerGlobal manager = DisplayManagerGlobal.getInstance();
return new DisplayInterface() {
@Override
public void registerDisplayListener(DisplayManager.DisplayListener listener) {
- manager.registerDisplayListener(listener, handler,
- DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
- | DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE,
+ manager.registerDisplayListener(listener, handler, displayEventsToBeSubscribed,
ActivityThread.currentPackageName());
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 6f7e5ad51b89..7cecc39dacde 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -66,6 +66,7 @@ import com.android.internal.protolog.IProtoLogConfigurationService.RegisterClien
import com.android.internal.protolog.common.ILogger;
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
+import com.android.internal.protolog.common.InvalidFormatStringException;
import com.android.internal.protolog.common.LogDataType;
import com.android.internal.protolog.common.LogLevel;
@@ -207,7 +208,12 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
@Override
public void log(LogLevel logLevel, IProtoLogGroup group, String messageString, Object... args) {
- log(logLevel, group, new Message(messageString), args);
+ try {
+ log(logLevel, group, new Message(messageString), args);
+ } catch (InvalidFormatStringException e) {
+ Slog.e(LOG_TAG, "Invalid protolog string format", e);
+ log(logLevel, group, new Message("INVALID MESSAGE"), new Object[0]);
+ }
}
/**
@@ -831,7 +837,7 @@ public abstract class PerfettoProtoLogImpl extends IProtoLogClient.Stub implemen
this.mMessageString = null;
}
- private Message(@NonNull String messageString) {
+ private Message(@NonNull String messageString) throws InvalidFormatStringException {
this.mMessageHash = null;
final List<Integer> argTypes = LogDataType.parseFormatString(messageString);
this.mMessageMask = LogDataType.logDataTypesToBitMask(argTypes);
diff --git a/core/java/com/android/internal/protolog/common/LogDataType.java b/core/java/com/android/internal/protolog/common/LogDataType.java
index c05824a58a77..10b07d9387de 100644
--- a/core/java/com/android/internal/protolog/common/LogDataType.java
+++ b/core/java/com/android/internal/protolog/common/LogDataType.java
@@ -86,8 +86,8 @@ public class LogDataType {
case '%':
break;
default:
- throw new InvalidFormatStringException("Invalid format string field"
- + " %${messageString[i + 1]}");
+ throw new InvalidFormatStringException("Invalid Protolog message format in "
+ + "\"" + messageString + "\" at index " + i + ".");
}
i += 2;
} else {
diff --git a/core/java/com/android/internal/statusbar/DisableStates.aidl b/core/java/com/android/internal/statusbar/DisableStates.aidl
new file mode 100644
index 000000000000..fd9882f0f7c2
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/DisableStates.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.internal.statusbar;
+
+parcelable DisableStates;
diff --git a/core/java/com/android/internal/statusbar/DisableStates.java b/core/java/com/android/internal/statusbar/DisableStates.java
new file mode 100644
index 000000000000..ca2fd6c03558
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/DisableStates.java
@@ -0,0 +1,95 @@
+/*
+ * 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.internal.statusbar;
+
+import android.app.StatusBarManager.Disable2Flags;
+import android.app.StatusBarManager.DisableFlags;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Pair;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Holds display ids with their disable flags.
+ */
+public class DisableStates implements Parcelable {
+
+ /**
+ * A map of display IDs (integers) with corresponding disable flags.
+ */
+ public Map<Integer, Pair<@DisableFlags Integer, @Disable2Flags Integer>> displaysWithStates;
+
+ /**
+ * Whether the disable state change should be animated.
+ */
+ public boolean animate;
+
+ public DisableStates(
+ Map<Integer, Pair<@DisableFlags Integer, @Disable2Flags Integer>> displaysWithStates,
+ boolean animate) {
+ this.displaysWithStates = displaysWithStates;
+ this.animate = animate;
+ }
+
+ public DisableStates(
+ Map<Integer, Pair<@DisableFlags Integer, @Disable2Flags Integer>> displaysWithStates) {
+ this(displaysWithStates, true);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(displaysWithStates.size()); // Write the size of the map
+ for (Map.Entry<Integer, Pair<Integer, Integer>> entry : displaysWithStates.entrySet()) {
+ dest.writeInt(entry.getKey());
+ dest.writeInt(entry.getValue().first);
+ dest.writeInt(entry.getValue().second);
+ }
+ dest.writeBoolean(animate);
+ }
+
+ /**
+ * Used to make this class parcelable.
+ */
+ public static final Parcelable.Creator<DisableStates> CREATOR = new Parcelable.Creator<>() {
+ @Override
+ public DisableStates createFromParcel(Parcel source) {
+ int size = source.readInt(); // Read the size of the map
+ Map<Integer, Pair<Integer, Integer>> displaysWithStates = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ int key = source.readInt();
+ int first = source.readInt();
+ int second = source.readInt();
+ displaysWithStates.put(key, new Pair<>(first, second));
+ }
+ final boolean animate = source.readBoolean();
+ return new DisableStates(displaysWithStates, animate);
+ }
+
+ @Override
+ public DisableStates[] newArray(int size) {
+ return new DisableStates[size];
+ }
+ };
+}
+
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 5a180d7358dd..ce9b036f2fd7 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -32,6 +32,7 @@ import android.os.UserHandle;
import android.view.KeyEvent;
import android.service.notification.StatusBarNotification;
+import com.android.internal.statusbar.DisableStates;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
import com.android.internal.statusbar.LetterboxDetails;
@@ -44,6 +45,7 @@ oneway interface IStatusBar
void setIcon(String slot, in StatusBarIcon icon);
void removeIcon(String slot);
void disable(int displayId, int state1, int state2);
+ void disableForAllDisplays(in DisableStates disableStates);
void animateExpandNotificationsPanel();
void animateExpandSettingsPanel(String subPanel);
void animateCollapsePanels();
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 3da19220248b..822ef34ad925 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -401,9 +401,19 @@ public class ConversationLayout extends FrameLayout
@RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
- mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed
- ? TextUtils.isEmpty(mSummarizedContent) ? 1 : MAX_SUMMARIZATION_LINES
- : Integer.MAX_VALUE);
+ int maxLines = Integer.MAX_VALUE;
+ if (isCollapsed) {
+ if (!TextUtils.isEmpty(mSummarizedContent)) {
+ maxLines = MAX_SUMMARIZATION_LINES;
+ } else {
+ if (android.app.Flags.nmCollapsedLines()) {
+ maxLines = 2;
+ } else {
+ maxLines = 1;
+ }
+ }
+ }
+ mMessagingLinearLayout.setMaxDisplayedLines(maxLines);
updateExpandButton();
updateContentEndPaddings();
}
@@ -1177,7 +1187,9 @@ public class ConversationLayout extends FrameLayout
nameOverride = mNameReplacement;
}
newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
- newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
+ newGroup.setSingleLine(mIsCollapsed
+ ? !android.app.Flags.nmCollapsedLines() && TextUtils.isEmpty(mSummarizedContent)
+ : false);
newGroup.setIsCollapsed(mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 4cc4b38f95a5..f9c8228e455d 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -157,6 +157,10 @@ public class MessagingLayout extends FrameLayout
@RemotableViewMethod(asyncImpl = "setIsCollapsedAsync")
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
+ if (mIsCollapsed) {
+ mMessagingLinearLayout.setMaxDisplayedLines(
+ android.app.Flags.nmCollapsedLines() ? 2 : 1);
+ }
}
/**
@@ -549,7 +553,9 @@ public class MessagingLayout extends FrameLayout
if (sender != mUser && mNameReplacement != null) {
nameOverride = mNameReplacement;
}
- newGroup.setSingleLine(mIsCollapsed && TextUtils.isEmpty(mSummarizedContent));
+ newGroup.setSingleLine(mIsCollapsed
+ ? !android.app.Flags.nmCollapsedLines() && TextUtils.isEmpty(mSummarizedContent)
+ : false);
newGroup.setShowingAvatar(!mIsCollapsed);
newGroup.setIsCollapsed(mIsCollapsed);
newGroup.setSender(sender, nameOverride);
diff --git a/core/java/com/android/server/servicewatcher/OWNERS b/core/java/com/android/server/servicewatcher/OWNERS
index ced619f05f1d..dbc598e1547f 100644
--- a/core/java/com/android/server/servicewatcher/OWNERS
+++ b/core/java/com/android/server/servicewatcher/OWNERS
@@ -1,5 +1,5 @@
# Bug component: 25692
-sooniln@google.com
+dnchrist@google.com
wyattriley@google.com
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index bfa0aa9638a9..7ed73d7668b9 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -210,7 +210,6 @@ cc_library_shared_for_libandroid_runtime {
"android_media_AudioAttributes.cpp",
"android_media_AudioProductStrategies.cpp",
"android_media_AudioVolumeGroups.cpp",
- "android_media_AudioVolumeGroupCallback.cpp",
"android_media_DeviceCallback.cpp",
"android_media_MediaMetricsJNI.cpp",
"android_media_MicrophoneInfo.cpp",
@@ -311,6 +310,7 @@ cc_library_shared_for_libandroid_runtime {
"audioflinger-aidl-cpp",
"audiopolicy-types-aidl-cpp",
"spatializer-aidl-cpp",
+ "volumegroupcallback-aidl-cpp",
"av-types-aidl-cpp",
"android.hardware.camera.device@3.2",
"camera_platform_flags_c_lib",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b2b826391e1d..1ff07745e904 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -101,7 +101,6 @@ extern int register_android_media_AudioTrack(JNIEnv *env);
extern int register_android_media_AudioAttributes(JNIEnv *env);
extern int register_android_media_AudioProductStrategies(JNIEnv *env);
extern int register_android_media_AudioVolumeGroups(JNIEnv *env);
-extern int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env);
extern int register_android_media_ImageReader(JNIEnv *env);
extern int register_android_media_ImageWriter(JNIEnv *env);
extern int register_android_media_MicrophoneInfo(JNIEnv *env);
@@ -1660,7 +1659,6 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_media_AudioAttributes),
REG_JNI(register_android_media_AudioProductStrategies),
REG_JNI(register_android_media_AudioVolumeGroups),
- REG_JNI(register_android_media_AudioVolumeGroupChangeHandler),
REG_JNI(register_android_media_ImageReader),
REG_JNI(register_android_media_ImageWriter),
REG_JNI(register_android_media_MediaMetrics),
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index b679688959b1..1bbf811dc373 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -20,16 +20,17 @@
#include <atomic>
#define LOG_TAG "AudioSystem-JNI"
+#include <android-base/properties.h>
#include <android/binder_ibinder_jni.h>
#include <android/binder_libbinder.h>
#include <android/media/AudioVibratorInfo.h>
+#include <android/media/INativeAudioVolumeGroupCallback.h>
#include <android/media/INativeSpatializerCallback.h>
#include <android/media/ISpatializer.h>
#include <android/media/audio/common/AudioConfigBase.h>
#include <android_media_audiopolicy.h>
#include <android_os_Parcel.h>
#include <audiomanager/AudioManager.h>
-#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <jni.h>
#include <media/AidlConversion.h>
@@ -41,14 +42,14 @@
#include <nativehelper/ScopedLocalRef.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <nativehelper/jni_macros.h>
+#include <sys/system_properties.h>
#include <system/audio.h>
#include <system/audio_policy.h>
-#include <sys/system_properties.h>
#include <utils/Log.h>
+#include <memory>
#include <optional>
#include <sstream>
-#include <memory>
#include <vector>
#include "android_media_AudioAttributes.h"
@@ -59,8 +60,8 @@
#include "android_media_AudioFormat.h"
#include "android_media_AudioMixerAttributes.h"
#include "android_media_AudioProfile.h"
-#include "android_media_MicrophoneInfo.h"
#include "android_media_JNIUtils.h"
+#include "android_media_MicrophoneInfo.h"
#include "android_util_Binder.h"
#include "core_jni_helpers.h"
@@ -3442,6 +3443,21 @@ static void android_media_AudioSystem_triggerSystemPropertyUpdate(JNIEnv *env,
}
}
+static int android_media_AudioSystem_registerAudioVolumeGroupCallback(
+ JNIEnv *env, jobject thiz, jobject jIAudioVolumeGroupCallback) {
+ sp<media::INativeAudioVolumeGroupCallback> nIAudioVolumeGroupCallback =
+ interface_cast<media::INativeAudioVolumeGroupCallback>(
+ ibinderForJavaObject(env, jIAudioVolumeGroupCallback));
+ return AudioSystem::addAudioVolumeGroupCallback(nIAudioVolumeGroupCallback);
+}
+
+static int android_media_AudioSystem_unregisterAudioVolumeGroupCallback(
+ JNIEnv *env, jobject thiz, jobject jIAudioVolumeGroupCallback) {
+ sp<media::INativeAudioVolumeGroupCallback> nIAudioVolumeGroupCallback =
+ interface_cast<media::INativeAudioVolumeGroupCallback>(
+ ibinderForJavaObject(env, jIAudioVolumeGroupCallback));
+ return AudioSystem::removeAudioVolumeGroupCallback(nIAudioVolumeGroupCallback);
+}
// ----------------------------------------------------------------------------
@@ -3612,6 +3628,12 @@ static const JNINativeMethod gMethods[] = {
MAKE_JNI_NATIVE_METHOD("clearPreferredMixerAttributes",
"(Landroid/media/AudioAttributes;II)I",
android_media_AudioSystem_clearPreferredMixerAttributes),
+ MAKE_JNI_NATIVE_METHOD("registerAudioVolumeGroupCallback",
+ "(Landroid/media/INativeAudioVolumeGroupCallback;)I",
+ android_media_AudioSystem_registerAudioVolumeGroupCallback),
+ MAKE_JNI_NATIVE_METHOD("unregisterAudioVolumeGroupCallback",
+ "(Landroid/media/INativeAudioVolumeGroupCallback;)I",
+ android_media_AudioSystem_unregisterAudioVolumeGroupCallback),
MAKE_AUDIO_SYSTEM_METHOD(supportsBluetoothVariableLatency),
MAKE_AUDIO_SYSTEM_METHOD(setBluetoothVariableLatencyEnabled),
MAKE_AUDIO_SYSTEM_METHOD(isBluetoothVariableLatencyEnabled),
diff --git a/core/jni/android_media_AudioVolumeGroupCallback.cpp b/core/jni/android_media_AudioVolumeGroupCallback.cpp
deleted file mode 100644
index d130a4bc68fa..000000000000
--- a/core/jni/android_media_AudioVolumeGroupCallback.cpp
+++ /dev/null
@@ -1,176 +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.
- */
-#undef ANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION // TODO:remove this and fix code
-
-//#define LOG_NDEBUG 0
-
-#define LOG_TAG "AudioVolumeGroupCallback-JNI"
-
-#include <utils/Log.h>
-#include <nativehelper/JNIHelp.h>
-#include "core_jni_helpers.h"
-
-#include "android_media_AudioVolumeGroupCallback.h"
-
-
-// ----------------------------------------------------------------------------
-using namespace android;
-
-static const char* const kAudioVolumeGroupChangeHandlerClassPathName =
- "android/media/audiopolicy/AudioVolumeGroupChangeHandler";
-
-static struct {
- jfieldID mJniCallback;
-} gAudioVolumeGroupChangeHandlerFields;
-
-static struct {
- jmethodID postEventFromNative;
-} gAudioVolumeGroupChangeHandlerMethods;
-
-static Mutex gLock;
-
-JNIAudioVolumeGroupCallback::JNIAudioVolumeGroupCallback(JNIEnv* env,
- jobject thiz,
- jobject weak_thiz)
-{
- jclass clazz = env->GetObjectClass(thiz);
- if (clazz == NULL) {
- ALOGE("Can't find class %s", kAudioVolumeGroupChangeHandlerClassPathName);
- return;
- }
- mClass = (jclass)env->NewGlobalRef(clazz);
-
- // We use a weak reference so the AudioVolumeGroupChangeHandler object can be garbage collected.
- // The reference is only used as a proxy for callbacks.
- mObject = env->NewGlobalRef(weak_thiz);
-}
-
-JNIAudioVolumeGroupCallback::~JNIAudioVolumeGroupCallback()
-{
- // remove global references
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- env->DeleteGlobalRef(mObject);
- env->DeleteGlobalRef(mClass);
-}
-
-void JNIAudioVolumeGroupCallback::onAudioVolumeGroupChanged(volume_group_t group, int flags)
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- ALOGV("%s volume group id %d", __FUNCTION__, group);
- env->CallStaticVoidMethod(mClass,
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
- mObject,
- AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED, group, flags, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-void JNIAudioVolumeGroupCallback::onServiceDied()
-{
- JNIEnv *env = AndroidRuntime::getJNIEnv();
- if (env == NULL) {
- return;
- }
- env->CallStaticVoidMethod(mClass,
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative,
- mObject,
- AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED, 0, 0, NULL);
- if (env->ExceptionCheck()) {
- ALOGW("An exception occurred while notifying an event.");
- env->ExceptionClear();
- }
-}
-
-static
-sp<JNIAudioVolumeGroupCallback> setJniCallback(JNIEnv* env,
- jobject thiz,
- const sp<JNIAudioVolumeGroupCallback>& callback)
-{
- Mutex::Autolock l(gLock);
- sp<JNIAudioVolumeGroupCallback> old = (JNIAudioVolumeGroupCallback*)env->GetLongField(
- thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback);
- if (callback.get()) {
- callback->incStrong((void*)setJniCallback);
- }
- if (old != 0) {
- old->decStrong((void*)setJniCallback);
- }
- env->SetLongField(thiz, gAudioVolumeGroupChangeHandlerFields.mJniCallback,
- (jlong)callback.get());
- return old;
-}
-
-static void
-android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup(JNIEnv *env,
- jobject thiz,
- jobject weak_this)
-{
- ALOGV("%s", __FUNCTION__);
- sp<JNIAudioVolumeGroupCallback> callback =
- new JNIAudioVolumeGroupCallback(env, thiz, weak_this);
-
- if (AudioSystem::addAudioVolumeGroupCallback(callback) == NO_ERROR) {
- setJniCallback(env, thiz, callback);
- }
-}
-
-static void
-android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize(JNIEnv *env, jobject thiz)
-{
- ALOGV("%s", __FUNCTION__);
- sp<JNIAudioVolumeGroupCallback> callback = setJniCallback(env, thiz, 0);
- if (callback != 0) {
- AudioSystem::removeAudioVolumeGroupCallback(callback);
- }
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gMethods[] = {
- {"native_setup", "(Ljava/lang/Object;)V",
- (void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerSetup},
- {"native_finalize", "()V",
- (void *)android_media_AudioVolumeGroupChangeHandler_eventHandlerFinalize},
-};
-
-int register_android_media_AudioVolumeGroupChangeHandler(JNIEnv *env)
-{
- jclass audioVolumeGroupChangeHandlerClass =
- FindClassOrDie(env, kAudioVolumeGroupChangeHandlerClassPathName);
- gAudioVolumeGroupChangeHandlerMethods.postEventFromNative =
- GetStaticMethodIDOrDie(env, audioVolumeGroupChangeHandlerClass, "postEventFromNative",
- "(Ljava/lang/Object;IIILjava/lang/Object;)V");
-
- gAudioVolumeGroupChangeHandlerFields.mJniCallback =
- GetFieldIDOrDie(env, audioVolumeGroupChangeHandlerClass, "mJniCallback", "J");
-
- env->DeleteLocalRef(audioVolumeGroupChangeHandlerClass);
-
- return RegisterMethodsOrDie(env,
- kAudioVolumeGroupChangeHandlerClassPathName,
- gMethods,
- NELEM(gMethods));
-}
-
diff --git a/core/jni/android_media_AudioVolumeGroupCallback.h b/core/jni/android_media_AudioVolumeGroupCallback.h
deleted file mode 100644
index de06549621b9..000000000000
--- a/core/jni/android_media_AudioVolumeGroupCallback.h
+++ /dev/null
@@ -1,44 +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.
- */
-
-#pragma once
-
-#include <system/audio.h>
-#include <media/AudioSystem.h>
-
-namespace android {
-
-// keep in sync with AudioManager.AudioVolumeGroupChangeHandler.java
-#define AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED 1000
-#define AUDIOVOLUMEGROUP_EVENT_SERVICE_DIED 1001
-
-class JNIAudioVolumeGroupCallback: public AudioSystem::AudioVolumeGroupCallback
-{
-public:
- JNIAudioVolumeGroupCallback(JNIEnv* env, jobject thiz, jobject weak_thiz);
- ~JNIAudioVolumeGroupCallback();
-
- void onAudioVolumeGroupChanged(volume_group_t group, int flags) override;
- void onServiceDied() override;
-
-private:
- void sendEvent(int event);
-
- jclass mClass; /**< Reference to AudioVolumeGroupChangeHandler class. */
- jobject mObject; /**< Weak ref to AudioVolumeGroupChangeHandler object to call on. */
-};
-
-} // namespace android
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e47adc90fc7a..7bb799a24ef1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1219,6 +1219,8 @@
6 - Lock if keyguard enabled or go to sleep (doze)
7 - Dream if possible or go to sleep (doze)
8 - Go to glanceable hub or dream if possible, or sleep if neither is available (doze)
+ 9 - Go to dream if device is not dreaming, stop dream if device is dreaming, or sleep if
+ neither is available (doze)
-->
<integer name="config_shortPressOnPowerBehavior">1</integer>
@@ -3207,6 +3209,10 @@
Note that HSUM devices without this enabled will not automatically have a main user. -->
<bool name="config_isMainUserPermanentAdmin">true</bool>
+ <!-- Whether all secondary users (and the system user) are eligible to have profiles.
+ If false, only the MainUser is eligible to have profiles. -->
+ <bool name="config_supportProfilesOnNonMainUser">false</bool>
+
<!-- Whether switch to headless system user is allowed. If allowed,
headless system user can run in the foreground even though it is not a full user. -->
<bool name="config_canSwitchToHeadlessSystemUser">false</bool>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index ef6b9188532e..1d40110dc7ca 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -86,7 +86,7 @@
CarrierConfigManager#KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_STRING_ARRAY.
If 0, the device always switch to the higher score SIM.
If < 0, the network type and signal strength based auto switch is disabled. -->
- <integer name="auto_data_switch_score_tolerance">4000</integer>
+ <integer name="auto_data_switch_score_tolerance">7000</integer>
<java-symbol type="integer" name="auto_data_switch_score_tolerance" />
<!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
@@ -261,7 +261,24 @@
to identify providers that should be ignored if the carrier config
carrier_supported_satellite_services_per_provider_bundle does not support them.
-->
- <string-array name="config_satellite_providers" translatable="false"></string-array>
+ <string-array name="config_satellite_providers" translatable="false">
+ <!-- T-Mobile - USA -->
+ <item>"310830"</item>
+ <!-- Entel Chile - Chile -->
+ <item>"73029"</item>
+ <!-- KDDI - Japan -->
+ <item>"44055"</item>
+ <!-- Kyivstar - Ukraine -->
+ <item>"255707"</item>
+ <!-- One NZ - New Zealand -->
+ <item>"53013"</item>
+ <!-- Optus - Australia -->
+ <item>"50559"</item>
+ <!-- Rogers - Canada -->
+ <item>"302723"</item>
+ <!-- Telstra - Australia -->
+ <item>"50511"</item>
+ </string-array>
<java-symbol type="array" name="config_satellite_providers" />
<!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a37ca2847638..2dc5687a1253 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -363,6 +363,7 @@
<java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
<java-symbol type="bool" name="config_useFixedVolume" />
<java-symbol type="bool" name="config_isMainUserPermanentAdmin"/>
+ <java-symbol type="bool" name="config_supportProfilesOnNonMainUser"/>
<java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_enableMultipleAdmins"/>
diff --git a/core/tests/FileSystemUtilsTest/OWNERS b/core/tests/FileSystemUtilsTest/OWNERS
new file mode 100644
index 000000000000..74eeacfeb973
--- /dev/null
+++ b/core/tests/FileSystemUtilsTest/OWNERS
@@ -0,0 +1,2 @@
+waghpawan@google.com
+kaleshsingh@google.com
diff --git a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
index 208d74e49afe..dbfd3e8ccdaa 100644
--- a/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
+++ b/core/tests/FileSystemUtilsTest/src/com/android/internal/content/FileSystemUtilsTest.java
@@ -38,6 +38,8 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
private static final String PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM =
"app_with_4kb_elf_no_override.apk";
+ private static final int DEVICE_WAIT_TIMEOUT = 120000;
+
@Test
@AppModeFull
public void runPunchedApp_embeddedNativeLibs() throws DeviceNotAvailableException {
@@ -98,8 +100,20 @@ public class FileSystemUtilsTest extends BaseHostJUnit4Test {
@AppModeFull
public void runAppWith4KbLib_compatByAlignmentChecks()
throws DeviceNotAvailableException, TargetSetupError {
+ // make sure that device is available for UI test
+ prepareDevice();
// This test is expected to fail since compat is disabled in manifest
runPageSizeCompatTest(PAGE_SIZE_COMPAT_ENABLED_BY_PLATFORM,
"testPageSizeCompat_compatByAlignmentChecks");
}
+
+ private void prepareDevice() throws DeviceNotAvailableException {
+ // Verify that device is online before running test and enable root
+ getDevice().waitForDeviceAvailable(DEVICE_WAIT_TIMEOUT);
+ getDevice().enableAdbRoot();
+ getDevice().waitForDeviceAvailable(DEVICE_WAIT_TIMEOUT);
+
+ getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+ getDevice().executeShellCommand("wm dismiss-keyguard");
+ }
}
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
index 8349659517c5..b63fcdc8362f 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheUnitTest.java
@@ -68,6 +68,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Unit tests for {@link android.content.pm.RegisteredServicesCache}
@@ -84,8 +85,8 @@ public class RegisteredServicesCacheUnitTest {
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
- private final ResolveInfo mResolveInfo1 = new ResolveInfo();
- private final ResolveInfo mResolveInfo2 = new ResolveInfo();
+ private final TestResolveInfo mResolveInfo1 = new TestResolveInfo();
+ private final TestResolveInfo mResolveInfo2 = new TestResolveInfo();
private final TestServiceType mTestServiceType1 = new TestServiceType("t1", "value1");
private final TestServiceType mTestServiceType2 = new TestServiceType("t2", "value2");
@Mock
@@ -195,13 +196,13 @@ public class RegisteredServicesCacheUnitTest {
reset(testServicesCache);
- testServicesCache.clearServicesForQuerying();
int u1uid = UserHandle.getUid(U1, UID1);
assertThat(u1uid).isNotEqualTo(UID1);
final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
mTestServiceType1, u1uid, mResolveInfo1.serviceInfo.getComponentName(),
1000L /* lastUpdateTime */);
+ mResolveInfo1.setResolveInfoId(U1);
testServicesCache.addServiceForQuerying(U1, mResolveInfo1, serviceInfo2);
testServicesCache.getAllServices(U1);
@@ -286,7 +287,7 @@ public class RegisteredServicesCacheUnitTest {
}
@Test
- public void testClearServiceInfoCachesAfterTimeout() throws Exception {
+ public void testClearServiceInfoCachesForSingleUserAfterTimeout() throws Exception {
PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
anyInt(), eq(U0))).thenReturn(packageInfo1);
@@ -316,6 +317,58 @@ public class RegisteredServicesCacheUnitTest {
verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
}
+ @Test
+ public void testClearServiceInfoCachesForMultiUserAfterTimeout() throws Exception {
+ PackageInfo packageInfo1 = createPackageInfo(1000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo1.serviceInfo.packageName),
+ anyInt(), eq(U0))).thenReturn(packageInfo1);
+ PackageInfo packageInfo2 = createPackageInfo(2000L /* lastUpdateTime */);
+ when(mMockPackageManager.getPackageInfoAsUser(eq(mResolveInfo2.serviceInfo.packageName),
+ anyInt(), eq(U1))).thenReturn(packageInfo2);
+
+ TestRegisteredServicesCache testServicesCache = spy(
+ new TestRegisteredServicesCache(mMockInjector, null /* serializerAndParser */));
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo1 = newServiceInfo(
+ mTestServiceType1, UID1, mResolveInfo1.serviceInfo.getComponentName(),
+ 1000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U0, mResolveInfo1, serviceInfo1);
+
+ int u1uid = UserHandle.getUid(U1, UID1);
+ final RegisteredServicesCache.ServiceInfo<TestServiceType> serviceInfo2 = newServiceInfo(
+ mTestServiceType2, u1uid, mResolveInfo2.serviceInfo.getComponentName(),
+ 2000L /* lastUpdateTime */);
+ testServicesCache.addServiceForQuerying(U1, mResolveInfo2, serviceInfo2);
+
+ // Don't invoke run on the Runnable for U0 user, and it will not clear the service info of
+ // U0 user. Invoke run on the Runnable for U1 user, and it will just clear the service info
+ // of U1 user.
+ doAnswer(invocation -> {
+ Message message = invocation.getArgument(0);
+ if (!message.obj.equals(Integer.valueOf(U0))) {
+ message.getCallback().run();
+ }
+ return true;
+ }).when(mMockBackgroundHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+ // It will generate the service info of U0 user into cache.
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+ // It will generate the service info of U1 user into cache.
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ verify(mMockBackgroundHandler, times(2)).sendMessageAtTime(any(Message.class), anyLong());
+
+ reset(testServicesCache);
+
+ testServicesCache.invalidateCache(U0);
+ testServicesCache.getAllServices(U0);
+ verify(testServicesCache, never()).parseServiceInfo(eq(mResolveInfo1), eq(1000L));
+
+ testServicesCache.invalidateCache(U1);
+ testServicesCache.getAllServices(U1);
+ verify(testServicesCache, times(1)).parseServiceInfo(eq(mResolveInfo2), eq(2000L));
+ }
+
private static RegisteredServicesCache.ServiceInfo<TestServiceType> newServiceInfo(
TestServiceType type, int uid, ComponentName componentName, long lastUpdateTime) {
final ComponentInfo info = new ComponentInfo();
@@ -324,7 +377,7 @@ public class RegisteredServicesCacheUnitTest {
return new RegisteredServicesCache.ServiceInfo<>(type, info, componentName, lastUpdateTime);
}
- private void addServiceInfoIntoResolveInfo(ResolveInfo resolveInfo, String packageName,
+ private void addServiceInfoIntoResolveInfo(TestResolveInfo resolveInfo, String packageName,
String serviceName) {
final ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.packageName = packageName;
@@ -345,7 +398,7 @@ public class RegisteredServicesCacheUnitTest {
static final String SERVICE_INTERFACE = "RegisteredServicesCacheUnitTest";
static final String SERVICE_META_DATA = "RegisteredServicesCacheUnitTest";
static final String ATTRIBUTES_NAME = "test";
- private SparseArray<Map<ResolveInfo, ServiceInfo<TestServiceType>>> mServices =
+ private SparseArray<Map<TestResolveInfo, ServiceInfo<TestServiceType>>> mServices =
new SparseArray<>();
public TestRegisteredServicesCache(Injector<TestServiceType> injector,
@@ -362,14 +415,14 @@ public class RegisteredServicesCacheUnitTest {
@Override
protected List<ResolveInfo> queryIntentServices(int userId) {
- Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId,
- new HashMap<ResolveInfo, ServiceInfo<TestServiceType>>());
+ Map<TestResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId,
+ new HashMap<TestResolveInfo, ServiceInfo<TestServiceType>>());
return new ArrayList<>(map.keySet());
}
- void addServiceForQuerying(int userId, ResolveInfo resolveInfo,
+ void addServiceForQuerying(int userId, TestResolveInfo resolveInfo,
ServiceInfo<TestServiceType> serviceInfo) {
- Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
+ Map<TestResolveInfo, ServiceInfo<TestServiceType>> map = mServices.get(userId);
if (map == null) {
map = new HashMap<>();
mServices.put(userId, map);
@@ -377,16 +430,12 @@ public class RegisteredServicesCacheUnitTest {
map.put(resolveInfo, serviceInfo);
}
- void clearServicesForQuerying() {
- mServices.clear();
- }
-
@Override
protected ServiceInfo<TestServiceType> parseServiceInfo(ResolveInfo resolveInfo,
long lastUpdateTime) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
- Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
+ Map<TestResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
ServiceInfo<TestServiceType> serviceInfo = map.get(resolveInfo);
if (serviceInfo != null) {
return serviceInfo;
@@ -400,4 +449,20 @@ public class RegisteredServicesCacheUnitTest {
super.onUserRemoved(userId);
}
}
+
+ /**
+ * Create different hash code with the same {@link android.content.pm.ResolveInfo} for testing.
+ */
+ public static class TestResolveInfo extends ResolveInfo {
+ int mResolveInfoId = 0;
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mResolveInfoId, serviceInfo);
+ }
+
+ public void setResolveInfoId(int resolveInfoId) {
+ mResolveInfoId = resolveInfoId;
+ }
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt b/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt
new file mode 100644
index 000000000000..e80d3a6e625f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/app/MediaRouteDialogPresenterTest.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.internal.app
+
+import android.content.Context
+import android.media.MediaRouter
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class MediaRouteDialogPresenterTest {
+ private var selectedRoute: MediaRouter.RouteInfo = mock()
+ private var mediaRouter: MediaRouter = mock<MediaRouter> {
+ on { selectedRoute } doReturn selectedRoute
+ }
+ private var context: Context = mock<Context> {
+ on { getSystemServiceName(MediaRouter::class.java) } doReturn Context.MEDIA_ROUTER_SERVICE
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeNotDefault_returnsFalse() {
+ selectedRoute.stub {
+ on { isDefault } doReturn false
+ on { matchesTypes(anyInt()) } doReturn true
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(false)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeDefault_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn true
+ on { matchesTypes(anyInt()) } doReturn true
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeNotMatch_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn false
+ on { matchesTypes(anyInt()) } doReturn false
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+
+ @Test
+ fun shouldShowChooserDialog_routeDefaultAndNotMatch_returnsTrue() {
+ selectedRoute.stub {
+ on { isDefault } doReturn true
+ on { matchesTypes(anyInt()) } doReturn false
+ }
+
+ assertThat(MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY))
+ .isEqualTo(true)
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/DisableStatesTest.java b/core/tests/coretests/src/com/android/internal/statusbar/DisableStatesTest.java
new file mode 100644
index 000000000000..5b82696b81c3
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/statusbar/DisableStatesTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.internal.statusbar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.os.Parcel;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DisableStatesTest {
+
+ @Test
+ public void testParcelable() {
+ Map<Integer, Pair<Integer, Integer>> displaysWithStates = new HashMap<>();
+ displaysWithStates.put(1, new Pair<>(10, 20));
+ displaysWithStates.put(2, new Pair<>(30, 40));
+ boolean animate = true;
+ DisableStates original = new DisableStates(displaysWithStates, animate);
+
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ DisableStates restored = DisableStates.CREATOR.createFromParcel(parcel);
+
+ assertNotNull(restored);
+ assertEquals(original.displaysWithStates.size(), restored.displaysWithStates.size());
+ for (Map.Entry<Integer, Pair<Integer, Integer>> entry :
+ original.displaysWithStates.entrySet()) {
+ int displayId = entry.getKey();
+ Pair<Integer, Integer> originalDisplayStates = entry.getValue();
+ Pair<Integer, Integer> restoredDisplayStates = restored.displaysWithStates.get(
+ displayId);
+ assertEquals(originalDisplayStates.first, restoredDisplayStates.first);
+ assertEquals(originalDisplayStates.second, restoredDisplayStates.second);
+ }
+ assertEquals(original.animate, restored.animate);
+ }
+}
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_restart.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_restart.xml
new file mode 100644
index 000000000000..d407884d3fcf
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_ic_handle_menu_restart.xml
@@ -0,0 +1,28 @@
+<?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.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="20dp"
+ android:height="20dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#1C1C14"
+ android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
+ <path
+ android:fillColor="#1C1C14"
+ android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
index dd1a1b1dca13..75ec2ab9f6f9 100644
--- a/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
+++ b/libs/WindowManager/Shell/res/drawable/desktop_windowing_transition_background.xml
@@ -18,15 +18,15 @@
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
<item android:id="@+id/indicator_solid">
<shape android:shape="rectangle">
- <solid android:color="@androidprv:color/materialColorPrimaryContainer" />
+ <solid android:color="@androidprv:color/materialColorPrimaryFixed" />
<corners android:radius="28dp" />
</shape>
</item>
<item android:id="@+id/indicator_stroke">
<shape android:shape="rectangle">
<corners android:radius="28dp" />
- <stroke android:width="1dp"
- android:color="@androidprv:color/materialColorPrimaryContainer"/>
+ <stroke android:width="2dp"
+ android:color="@androidprv:color/materialColorPrimaryFixed"/>
</shape>
</item>
</layer-list>
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
index c33669636be4..30acf1ac6eda 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_handle_menu.xml
@@ -163,6 +163,13 @@
android:text="@string/change_aspect_ratio_text"
android:src="@drawable/desktop_mode_ic_handle_menu_change_aspect_ratio"
style="@style/DesktopModeHandleMenuActionButton"/>
+
+ <com.android.wm.shell.windowdecor.HandleMenuActionButton
+ android:id="@+id/handle_menu_restart_button"
+ android:contentDescription="@string/handle_menu_restart_text"
+ android:text="@string/handle_menu_restart_text"
+ android:src="@drawable/desktop_mode_ic_handle_menu_restart"
+ style="@style/DesktopModeHandleMenuActionButton"/>
</LinearLayout>
<LinearLayout
diff --git a/libs/WindowManager/Shell/res/values/colors.xml b/libs/WindowManager/Shell/res/values/colors.xml
index 8d18f959951b..5732fc936b47 100644
--- a/libs/WindowManager/Shell/res/values/colors.xml
+++ b/libs/WindowManager/Shell/res/values/colors.xml
@@ -68,8 +68,6 @@
<color name="desktop_mode_caption_button_on_hover_light">#11000000</color>
<color name="desktop_mode_caption_button_on_hover_dark">#11FFFFFF</color>
<color name="desktop_mode_caption_button">#00000000</color>
- <color name="tiling_divider_background_light">#C9C7B6</color>
- <color name="tiling_divider_background_dark">#4A4739</color>
<color name="tiling_handle_background_light">#000000</color>
<color name="tiling_handle_background_dark">#FFFFFF</color>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index e68680219349..ca18c97f9127 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -304,6 +304,8 @@
<dimen name="bubble_transform_area_width">140dp</dimen>
<!-- Width of the box at the corner of the screen where drag leads to app moving to bubble -->
<dimen name="bubble_transform_area_height">140dp</dimen>
+ <!-- How much elevation a bubble ui needs when dragged, must be above drop target & dismiss. -->
+ <dimen name="dragged_bubble_elevation">3dp</dimen>
<!-- Bottom and end margin for compat buttons. -->
<dimen name="compat_button_margin">24dp</dimen>
@@ -534,10 +536,10 @@
pill elevation. -->
<dimen name="desktop_mode_handle_menu_width">218dp</dimen>
- <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each plus
- additional actions pill 208dp plus 2dp spacing between them plus 4dp top padding
- plus 2dp bottom padding: 52*3 + 52*4 + (4-1)*2 + 4 + 2 = 376 -->
- <dimen name="desktop_mode_handle_menu_height">376dp</dimen>
+ <!-- The maximum height of the handle menu in desktop mode. Three pills at 52dp each,
+ additional actions pill 260dp, plus 2dp spacing between them plus 4dp top padding.
+ 52*3 + 52*5 + (5-1)*2 + 4 = 428 -->
+ <dimen name="desktop_mode_handle_menu_height">428dp</dimen>
<!-- The elevation set on the handle menu pills. -->
<dimen name="desktop_mode_handle_menu_pill_elevation">1dp</dimen>
@@ -566,6 +568,9 @@
<!-- The height of the handle menu's "Change aspect ratio" pill in desktop mode. -->
<dimen name="desktop_mode_handle_menu_change_aspect_ratio_height">52dp</dimen>
+ <!-- The height of the handle menu's "Optimize View" pill in desktop mode. -->
+ <dimen name="desktop_mode_handle_menu_restart_button_height">52dp</dimen>
+
<!-- The margin between pills of the handle menu in desktop mode. -->
<dimen name="desktop_mode_handle_menu_pill_spacing_margin">2dp</dimen>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 5ef83826840b..1fd4704f7814 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -314,6 +314,8 @@
<string name="manage_windows_text">Manage Windows</string>
<!-- Accessibility text for the handle menu change aspect ratio button [CHAR LIMIT=NONE] -->
<string name="change_aspect_ratio_text">Change aspect ratio</string>
+ <!-- Accessibility text for the handle menu restart button [CHAR LIMIT=NONE] -->
+ <string name="handle_menu_restart_text">Optimize View</string>
<!-- Accessibility text for the handle menu close button [CHAR LIMIT=NONE] -->
<string name="close_text">Close</string>
<!-- Accessibility text for the handle menu close menu 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 3b504cf713f1..74b6023bde36 100644
--- a/libs/WindowManager/Shell/shared/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/shared/res/values/dimen.xml
@@ -39,9 +39,9 @@
<dimen name="drag_zone_v_split_from_expanded_view_height_fold_short">100dp</dimen>
<!-- Bubble drop target dimensions -->
- <dimen name="drop_target_elevation">1dp</dimen>
+ <dimen name="drop_target_elevation">2dp</dimen>
<dimen name="drop_target_radius">28dp</dimen>
- <dimen name="drop_target_stroke">1dp</dimen>
+ <dimen name="drop_target_stroke">2dp</dimen>
<dimen name="drop_target_full_screen_padding">20dp</dimen>
<dimen name="drop_target_desktop_window_padding_small">100dp</dimen>
<dimen name="drop_target_desktop_window_padding_large">130dp</dimen>
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
index 73277310ffe4..df101fe44b75 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DropTargetView.kt
@@ -30,15 +30,15 @@ import com.android.wm.shell.shared.R
class DropTargetView(context: Context) : View(context) {
private val rectPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
style = Paint.Style.FILL
alpha = (0.35f * 255).toInt()
}
private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
- color = context.getColor(com.android.internal.R.color.materialColorPrimaryContainer)
+ color = context.getColor(com.android.internal.R.color.materialColorPrimaryFixed)
style = Paint.Style.STROKE
- strokeWidth = 1.dpToPx()
+ strokeWidth = 2.dpToPx()
}
private val cornerRadius = 28.dpToPx()
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index ed5e0c608675..a9224b02ad31 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -278,6 +278,9 @@ public class DesktopModeStatus {
if (!canEnterDesktopMode(context)) {
return false;
}
+ if (!enforceDeviceRestrictions()) {
+ return true;
+ }
if (display.getType() == Display.TYPE_INTERNAL) {
return canInternalDisplayHostDesktops(context);
}
@@ -448,6 +451,6 @@ public class DesktopModeStatus {
pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1));
pw.print(innerPrefix); pw.print("showAppHandle config override=");
- pw.print(overridesShowAppHandle(context));
+ pw.println(overridesShowAppHandle(context));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
index 8e78686ac13d..8e3dc4c36c1d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/SizeChangeAnimation.java
@@ -66,7 +66,7 @@ public class SizeChangeAnimation {
* The maximum of stretching applied to any surface during interpolation (since the animation
* is a combination of stretching/cropping/fading).
*/
- private static final float SCALE_FACTOR = 0.7f;
+ private static final float DEFAULT_SCALE_FACTOR = 0.7f;
/**
* Since this animation is made of several sub-animations, we want to pre-arrange the
@@ -82,13 +82,27 @@ public class SizeChangeAnimation {
*/
private static final int ANIMATION_RESOLUTION = 1000;
+ /**
+ * Initialize a size-change animation from start to end bounds
+ */
public SizeChangeAnimation(Rect startBounds, Rect endBounds) {
- this(startBounds, endBounds, 1f);
+ this(startBounds, endBounds, 1f, DEFAULT_SCALE_FACTOR);
}
- public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale) {
- mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale);
- mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds);
+ /**
+ * Initialize a size-change animation from start to end bounds.
+ * <p>
+ * Allows specifying the initial scale factor, {@code initialScale}, that is applied to the
+ * start bounds. This can be useful for example when a task is scaled down when the size change
+ * animation starts.
+ * <p>
+ * By default the max scale applied to any surface is {@link #DEFAULT_SCALE_FACTOR}. Use
+ * {@code scaleFactor} to override it.
+ */
+ public SizeChangeAnimation(Rect startBounds, Rect endBounds, float initialScale,
+ float scaleFactor) {
+ mAnimation = buildContainerAnimation(startBounds, endBounds, initialScale, scaleFactor);
+ mSnapshotAnim = buildSnapshotAnimation(startBounds, endBounds, scaleFactor);
}
/**
@@ -172,15 +186,15 @@ public class SizeChangeAnimation {
/** Animation for the whole container (snapshot is inside this container). */
private static AnimationSet buildContainerAnimation(Rect startBounds, Rect endBounds,
- float initialScale) {
+ float initialScale, float scaleFactor) {
final long duration = ANIMATION_RESOLUTION;
boolean growing = endBounds.width() - startBounds.width()
+ endBounds.height() - startBounds.height() >= 0;
- long scalePeriod = (long) (duration * SCALE_FACTOR);
- float startScaleX = SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
- + (1.f - SCALE_FACTOR);
- float startScaleY = SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
- + (1.f - SCALE_FACTOR);
+ long scalePeriod = (long) (duration * scaleFactor);
+ float startScaleX = scaleFactor * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - scaleFactor);
+ float startScaleY = scaleFactor * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - scaleFactor);
final AnimationSet animSet = new AnimationSet(true);
final Animation scaleAnim = new ScaleAnimation(startScaleX, 1, startScaleY, 1);
@@ -218,15 +232,16 @@ public class SizeChangeAnimation {
}
/** The snapshot surface is assumed to be a child of the container surface. */
- private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds) {
+ private static AnimationSet buildSnapshotAnimation(Rect startBounds, Rect endBounds,
+ float scaleFactor) {
final long duration = ANIMATION_RESOLUTION;
boolean growing = endBounds.width() - startBounds.width()
+ endBounds.height() - startBounds.height() >= 0;
- long scalePeriod = (long) (duration * SCALE_FACTOR);
- float endScaleX = 1.f / (SCALE_FACTOR * ((float) startBounds.width()) / endBounds.width()
- + (1.f - SCALE_FACTOR));
- float endScaleY = 1.f / (SCALE_FACTOR * ((float) startBounds.height()) / endBounds.height()
- + (1.f - SCALE_FACTOR));
+ long scalePeriod = (long) (duration * scaleFactor);
+ float endScaleX = 1.f / (scaleFactor * ((float) startBounds.width()) / endBounds.width()
+ + (1.f - scaleFactor));
+ float endScaleY = 1.f / (scaleFactor * ((float) startBounds.height()) / endBounds.height()
+ + (1.f - scaleFactor));
AnimationSet snapAnimSet = new AnimationSet(true);
// Animation for the "old-state" snapshot that is atop the task.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 5d59af940da0..81cf031994a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -508,7 +508,11 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
mShouldStartOnNextMoveEvent = false;
} else {
- mShouldStartOnNextMoveEvent = true;
+ if (predictiveBackDelayWmTransition()) {
+ onGestureStarted(touchX, touchY, swipeEdge);
+ } else {
+ mShouldStartOnNextMoveEvent = true;
+ }
}
}
} else if (keyAction == MotionEvent.ACTION_MOVE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index fa22a3961002..b89bfd5c969e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -596,11 +596,11 @@ public class BubbleBarAnimationHelper {
final Size size = getExpandedViewSize();
Point position = getExpandedViewRestPosition(size);
- final SizeChangeAnimation sca =
- new SizeChangeAnimation(
- new Rect(origBounds.left - position.x, origBounds.top - position.y,
- origBounds.right - position.x, origBounds.bottom - position.y),
- new Rect(0, 0, size.getWidth(), size.getHeight()), origScale);
+ Rect startBounds = new Rect(origBounds.left - position.x, origBounds.top - position.y,
+ origBounds.right - position.x, origBounds.bottom - position.y);
+ Rect endBounds = new Rect(0, 0, size.getWidth(), size.getHeight());
+ final SizeChangeAnimation sca = new SizeChangeAnimation(startBounds, endBounds,
+ origScale, /* scaleFactor= */ 1f);
sca.initialize(bbev, taskLeash, snapshot, startT);
Animator a = sca.buildViewAnimator(bbev, tvSf, snapshot, /* onFinish */ (va) -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index 35435569d8b1..44d859dfb9ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -17,9 +17,11 @@
package com.android.wm.shell.bubbles.bar
import android.annotation.SuppressLint
+import android.content.Context
import android.view.MotionEvent
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.DismissView
@@ -32,6 +34,7 @@ import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
/** Controller for handling drag interactions with [BubbleBarExpandedView] */
@SuppressLint("ClickableViewAccessibility")
class BubbleBarExpandedViewDragController(
+ private val context: Context,
private val expandedView: BubbleBarExpandedView,
private val dismissView: DismissView,
private val animationHelper: BubbleBarAnimationHelper,
@@ -54,6 +57,8 @@ class BubbleBarExpandedViewDragController(
MagnetizedObject.magnetizeView(expandedView)
private val magnetizedDismissTarget: MagnetizedObject.MagneticTarget
+ private val draggedBubbleElevation: Float
+
init {
magnetizedExpandedView.magnetListener = MagnetListener()
magnetizedExpandedView.animateStuckToTarget =
@@ -70,6 +75,8 @@ class BubbleBarExpandedViewDragController(
MagnetizedObject.MagneticTarget(dismissView.circle, dismissView.circle.width)
magnetizedExpandedView.addTarget(magnetizedDismissTarget)
+ draggedBubbleElevation = context.resources.getDimension(
+ R.dimen.dragged_bubble_elevation)
val dragMotionEventHandler = HandleDragListener()
expandedView.handleView.setOnTouchListener { view, event ->
@@ -103,6 +110,7 @@ class BubbleBarExpandedViewDragController(
override fun onDown(v: View, ev: MotionEvent): Boolean {
// While animating, don't allow new touch events
if (expandedView.isAnimating) return false
+ expandedView.z = draggedBubbleElevation
if (dropTargetManager != null && dragZoneFactory != null) {
val draggedObject = DraggedObject.ExpandedView(
if (bubblePositioner.isBubbleBarOnLeft) {
@@ -154,11 +162,13 @@ class BubbleBarExpandedViewDragController(
velX: Float,
velY: Float,
) {
+ v.translationZ = 0f
finishDrag()
}
override fun onCancel(v: View, ev: MotionEvent, viewInitialX: Float, viewInitialY: Float) {
isStuckToDismiss = false
+ v.translationZ = 0f
finishDrag()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 2cc9387bd1e9..dd86725f7944 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -349,6 +349,7 @@ public class BubbleBarLayerView extends FrameLayout
}
};
mDragController = new BubbleBarExpandedViewDragController(
+ mContext,
mExpandedView,
mDismissView,
mAnimationHelper,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 97184c859d4d..46f6e40ec5e8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -287,7 +287,13 @@ public class DisplayController {
? mContext
: mContext.createDisplayContext(display);
final Context context = perDisplayContext.createConfigurationContext(newConfig);
- dr.setDisplayLayout(context, new DisplayLayout(context, display));
+ final DisplayLayout displayLayout = new DisplayLayout(context, display);
+ if (mDisplayTopology != null) {
+ displayLayout.setGlobalBoundsDp(
+ mDisplayTopology.getAbsoluteBounds().get(
+ displayId, displayLayout.globalBoundsDp()));
+ }
+ dr.setDisplayLayout(context, displayLayout);
for (int i = 0; i < mDisplayChangedListeners.size(); ++i) {
mDisplayChangedListeners.get(i).onDisplayConfigurationChanged(
displayId, newConfig);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
deleted file mode 100644
index 1128fb2259b2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * 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.wm.shell.common.pip;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import android.window.DesktopExperienceFlags;
-import android.window.DesktopModeFlags;
-import android.window.DisplayAreaInfo;
-
-import com.android.wm.shell.Flags;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
-
-import java.util.Optional;
-
-/** Helper class for PiP on Desktop Mode. */
-public class PipDesktopState {
- private final PipDisplayLayoutState mPipDisplayLayoutState;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DragToDesktopTransitionHandler> mDragToDesktopTransitionHandlerOptional;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-
- public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DragToDesktopTransitionHandler> dragToDesktopTransitionHandlerOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
- mPipDisplayLayoutState = pipDisplayLayoutState;
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDragToDesktopTransitionHandlerOptional = dragToDesktopTransitionHandlerOptional;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
- }
-
- /**
- * Returns whether PiP in Desktop Windowing is enabled by checking the following:
- * - PiP in Desktop Windowing flag is enabled
- * - DesktopUserRepositories is injected
- * - DragToDesktopTransitionHandler is injected
- */
- public boolean isDesktopWindowingPipEnabled() {
- return DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue()
- && mDesktopUserRepositoriesOptional.isPresent()
- && mDragToDesktopTransitionHandlerOptional.isPresent();
- }
-
- /**
- * Returns whether PiP in Connected Displays is enabled by checking the following:
- * - PiP in Connected Displays flag is enabled
- * - PiP2 flag is enabled
- */
- public boolean isConnectedDisplaysPipEnabled() {
- return DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue() && Flags.enablePip2();
- }
-
- /** Returns whether the display with the PiP task is in freeform windowing mode. */
- private boolean isDisplayInFreeform() {
- final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- mPipDisplayLayoutState.getDisplayId());
- if (tdaInfo != null) {
- return tdaInfo.configuration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FREEFORM;
- }
- return false;
- }
-
- /** Returns whether PiP is active in a display that is in active Desktop Mode session. */
- public boolean isPipInDesktopMode() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- final int displayId = mPipDisplayLayoutState.getDisplayId();
- return mDesktopUserRepositoriesOptional.get().getCurrent().isAnyDeskActive(displayId);
- }
-
- /**
- * The windowing mode to restore to when resizing out of PIP direction.
- * Defaults to undefined and can be overridden to restore to an alternate windowing mode.
- */
- public int getOutPipWindowingMode() {
- // If we are exiting PiP while the device is in Desktop mode (the task should expand to
- // freeform windowing mode):
- // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
- // resolve the windowing mode to the display's windowing mode.
- // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
- if (isPipInDesktopMode()) {
- if (isDisplayInFreeform()) {
- return WINDOWING_MODE_UNDEFINED;
- } else {
- return WINDOWING_MODE_FREEFORM;
- }
- }
-
- // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
- return WINDOWING_MODE_UNDEFINED;
- }
-
- /** Returns whether there is a drag-to-desktop transition in progress. */
- public boolean isDragToDesktopInProgress() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!isDesktopWindowingPipEnabled()) {
- return false;
- }
- return mDragToDesktopTransitionHandlerOptional.get().getInProgress();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.kt
new file mode 100644
index 000000000000..55bde8906b63
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.wm.shell.common.pip
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.window.DesktopExperienceFlags
+import android.window.DesktopModeFlags
+import com.android.wm.shell.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler
+import java.util.Optional
+
+/** Helper class for PiP on Desktop Mode. */
+class PipDesktopState(
+ private val pipDisplayLayoutState: PipDisplayLayoutState,
+ private val desktopUserRepositoriesOptional: Optional<DesktopUserRepositories>,
+ private val dragToDesktopTransitionHandlerOptional: Optional<DragToDesktopTransitionHandler>,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+) {
+ /**
+ * Returns whether PiP in Desktop Windowing is enabled by checking the following:
+ * - PiP in Desktop Windowing flag is enabled
+ * - DesktopUserRepositories is present
+ * - DragToDesktopTransitionHandler is present
+ */
+ fun isDesktopWindowingPipEnabled(): Boolean =
+ DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_PIP.isTrue &&
+ desktopUserRepositoriesOptional.isPresent &&
+ dragToDesktopTransitionHandlerOptional.isPresent
+
+ /**
+ * Returns whether PiP in Connected Displays is enabled by checking the following:
+ * - PiP in Connected Displays flag is enabled
+ * - PiP2 flag is enabled
+ */
+ fun isConnectedDisplaysPipEnabled(): Boolean =
+ DesktopExperienceFlags.ENABLE_CONNECTED_DISPLAYS_PIP.isTrue && Flags.enablePip2()
+
+ /** Returns whether the display with the PiP task is in freeform windowing mode. */
+ private fun isDisplayInFreeform(): Boolean {
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ pipDisplayLayoutState.displayId
+ )
+
+ return tdaInfo?.configuration?.windowConfiguration?.windowingMode == WINDOWING_MODE_FREEFORM
+ }
+
+ /** Returns whether PiP is active in a display that is in active Desktop Mode session. */
+ fun isPipInDesktopMode(): Boolean {
+ if (!isDesktopWindowingPipEnabled()) {
+ return false
+ }
+
+ val displayId = pipDisplayLayoutState.displayId
+ return desktopUserRepositoriesOptional.get().current.isAnyDeskActive(displayId)
+ }
+
+ /** Returns the windowing mode to restore to when resizing out of PIP direction. */
+ // TODO(b/403345629): Update this for Multi-Desktop.
+ fun getOutPipWindowingMode(): Int {
+ // If we are exiting PiP while the device is in Desktop mode, the task should expand to
+ // freeform windowing mode.
+ // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
+ if (isPipInDesktopMode()) {
+ return if (isDisplayInFreeform()) {
+ WINDOWING_MODE_UNDEFINED
+ } else {
+ WINDOWING_MODE_FREEFORM
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED
+ }
+
+ /** Returns whether there is a drag-to-desktop transition in progress. */
+ fun isDragToDesktopInProgress(): Boolean =
+ isDesktopWindowingPipEnabled() && dragToDesktopTransitionHandlerOptional.get().inProgress
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
index cf0ecae7c815..a1e7ff04347f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerRoundedCorner.java
@@ -106,31 +106,37 @@ public class DividerRoundedCorner extends View {
* of non split screen.
*
* @param isSplitScreen Whether the divider is used by split screen or tiling.
- * @param isDarkMode Whether the mode is ui dark mode.
+ * @param color Rounded corner color.
*/
- public void setup(boolean isSplitScreen, boolean isDarkMode) {
+ public void setup(boolean isSplitScreen, int color) {
mIsSplitScreen = isSplitScreen;
if (!isSplitScreen) {
- mDividerBarBackground.setColor(getTilingHandleColor(isDarkMode));
+ mDividerBarBackground.setColor(color);
}
}
/**
- * Notifies the divider of ui mode change.
+ * Notifies the divider of ui mode change and provides a new color.
*
- * @param isDarkMode Whether the mode is ui dark mode.
+ * @param color The new divider rounded corner color.
*/
- public void onUiModeChange(boolean isDarkMode) {
+ public void onUiModeChange(int color) {
if (!mIsSplitScreen) {
- mDividerBarBackground.setColor(getTilingHandleColor(isDarkMode));
+ mDividerBarBackground.setColor(color);
invalidate();
}
}
- private int getTilingHandleColor(boolean isDarkMode) {
- return isDarkMode ? getResources().getColor(
- R.color.tiling_divider_background_dark, null /* theme */) : getResources().getColor(
- R.color.tiling_divider_background_light, null /* theme */);
+ /**
+ * Notifies rounded corner view of color change.
+ *
+ * @param color The new divider rounded corner color.
+ */
+ public void onCornerColorChange(int color) {
+ if (!mIsSplitScreen) {
+ mDividerBarBackground.setColor(color);
+ invalidate();
+ }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
index 9fa162164e0e..d9a66e1d64b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/FlexParallaxSpec.java
@@ -57,6 +57,11 @@ public class FlexParallaxSpec implements ParallaxSpec {
* @return 0f = no dim applied. 1f = full black.
*/
public float getDimValue(int position, DividerSnapAlgorithm snapAlgorithm) {
+ // On tablets, apps don't go offscreen, so only dim for dismissal.
+ if (!snapAlgorithm.areOffscreenRatiosSupported()) {
+ return ParallaxSpec.super.getDimValue(position, snapAlgorithm);
+ }
+
int startDismissPos = snapAlgorithm.getDismissStartTarget().getPosition();
int firstTargetPos = snapAlgorithm.getFirstSplitTarget().getPosition();
int middleTargetPos = snapAlgorithm.getMiddleTarget().getPosition();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 4413c8715c0d..d5f4a3885dbb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -112,6 +112,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
new SparseArray<>(0);
/**
+ * {@link SparseArray} that maps task ids to {@link CompatUIInfo}.
+ */
+ private final SparseArray<CompatUIInfo> mTaskIdToCompatUIInfoMap =
+ new SparseArray<>(0);
+
+ /**
* {@link Set} of task ids for which we need to display a restart confirmation dialog
*/
private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();
@@ -261,7 +267,11 @@ public class CompatUIController implements OnDisplaysChangedListener,
private void handleDisplayCompatShowRestartDialog(
CompatUIRequests.DisplayCompatShowRestartDialog request) {
- onRestartButtonClicked(new Pair<>(request.getTaskInfo(), request.getTaskListener()));
+ final CompatUIInfo compatUIInfo = mTaskIdToCompatUIInfoMap.get(request.getTaskId());
+ if (compatUIInfo == null) {
+ return;
+ }
+ onRestartButtonClicked(new Pair<>(compatUIInfo.getTaskInfo(), compatUIInfo.getListener()));
}
/**
@@ -273,6 +283,11 @@ public class CompatUIController implements OnDisplaysChangedListener,
public void onCompatInfoChanged(@NonNull CompatUIInfo compatUIInfo) {
final TaskInfo taskInfo = compatUIInfo.getTaskInfo();
final ShellTaskOrganizer.TaskListener taskListener = compatUIInfo.getListener();
+ if (taskListener == null) {
+ mTaskIdToCompatUIInfoMap.delete(taskInfo.taskId);
+ } else {
+ mTaskIdToCompatUIInfoMap.put(taskInfo.taskId, compatUIInfo);
+ }
final boolean isInDisplayCompatMode =
taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove();
if (taskInfo != null && !taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
index da4fc99491dc..b7af596ee0ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/CompatUIRequests.kt
@@ -16,8 +16,6 @@
package com.android.wm.shell.compatui.impl
-import android.app.TaskInfo
-import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.compatui.api.CompatUIRequest
internal const val DISPLAY_COMPAT_SHOW_RESTART_DIALOG = 0
@@ -27,7 +25,6 @@ internal const val DISPLAY_COMPAT_SHOW_RESTART_DIALOG = 0
*/
sealed class CompatUIRequests(override val requestId: Int) : CompatUIRequest {
/** Sent when the restart handle menu is clicked, and a restart dialog is requested. */
- data class DisplayCompatShowRestartDialog(val taskInfo: TaskInfo,
- val taskListener: ShellTaskOrganizer.TaskListener) :
+ data class DisplayCompatShowRestartDialog(val taskId: Int) :
CompatUIRequests(DISPLAY_COMPAT_SHOW_RESTART_DIALOG)
}
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 2cf671b0f446..5fbbb0bf1e78 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
@@ -79,6 +79,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.UserProfileContexts;
import com.android.wm.shell.common.split.SplitState;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxCommandHandler;
import com.android.wm.shell.compatui.letterbox.LetterboxTransitionObserver;
import com.android.wm.shell.crashhandling.ShellCrashHandler;
@@ -89,6 +90,7 @@ import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopDisplayModeController;
+import com.android.wm.shell.desktopmode.DesktopImeHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMinimizationTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
@@ -1044,7 +1046,8 @@ public abstract class WMShellModule {
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
- MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
+ Optional<CompatUIHandler> compatUI
) {
if (!DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)) {
return Optional.empty();
@@ -1062,7 +1065,7 @@ public abstract class WMShellModule {
activityOrientationChangeHandler, focusTransitionObserver, desktopModeEventLogger,
desktopModeUiEventLogger, taskResourceLoader, recentsTransitionHandler,
desktopModeCompatPolicy, desktopTilingDecorViewModel,
- multiDisplayDragMoveIndicatorController));
+ multiDisplayDragMoveIndicatorController, compatUI.orElse(null)));
}
@WMSingleton
@@ -1213,7 +1216,8 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
TaskStackListenerImpl taskStackListener,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
- @DynamicOverride DesktopUserRepositories desktopUserRepositories) {
+ @DynamicOverride DesktopUserRepositories desktopUserRepositories,
+ DisplayController displayController) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopActivityOrientationChangeHandler(
@@ -1222,7 +1226,8 @@ public abstract class WMShellModule {
shellTaskOrganizer,
taskStackListener,
toggleResizeDesktopTaskTransitionHandler,
- desktopUserRepositories));
+ desktopUserRepositories,
+ displayController));
}
return Optional.empty();
}
@@ -1341,7 +1346,9 @@ public abstract class WMShellModule {
Context context,
ShellInit shellInit,
@ShellMainThread CoroutineScope mainScope,
+ ShellController shellController,
DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
Optional<DesktopUserRepositories> desktopUserRepositories,
Optional<DesktopTasksController> desktopTasksController,
Optional<DesktopDisplayModeController> desktopDisplayModeController,
@@ -1355,7 +1362,9 @@ public abstract class WMShellModule {
context,
shellInit,
mainScope,
+ shellController,
displayController,
+ rootTaskDisplayAreaOrganizer,
desktopRepositoryInitializer,
desktopUserRepositories.get(),
desktopTasksController.get(),
@@ -1518,6 +1527,18 @@ public abstract class WMShellModule {
mainHandler));
}
+ @WMSingleton
+ @Provides
+ static Optional<DesktopImeHandler> provideDesktopImeHandler(
+ DisplayImeController displayImeController,
+ Context context,
+ ShellInit shellInit) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)) {
+ return Optional.empty();
+ }
+ return Optional.of(new DesktopImeHandler(displayImeController, shellInit));
+ }
+
//
// App zoom out
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
index b8f4bb8d8323..39ce5d9023a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandler.kt
@@ -23,10 +23,10 @@ import android.content.pm.ActivityInfo.ScreenOrientation
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.content.res.Configuration.ORIENTATION_PORTRAIT
import android.graphics.Rect
-import android.util.Size
import android.window.WindowContainerTransaction
import com.android.window.flags.Flags
import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.TaskStackListenerCallback
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -40,6 +40,7 @@ class DesktopActivityOrientationChangeHandler(
private val taskStackListener: TaskStackListenerImpl,
private val resizeHandler: ToggleResizeDesktopTaskTransitionHandler,
private val desktopUserRepositories: DesktopUserRepositories,
+ private val displayController: DisplayController,
) {
init {
@@ -101,12 +102,24 @@ class DesktopActivityOrientationChangeHandler(
orientation == ORIENTATION_LANDSCAPE &&
ActivityInfo.isFixedOrientationPortrait(requestedOrientation)
) {
+ val displayLayout = displayController.getDisplayLayout(task.displayId) ?: return
+ val captionInsets =
+ task.configuration.windowConfiguration.appBounds?.let {
+ it.top - task.configuration.windowConfiguration.bounds.top
+ } ?: 0
+ val newOrientationBounds =
+ calculateInitialBounds(
+ displayLayout = displayLayout,
+ taskInfo = task,
+ captionInsets = captionInsets,
+ requestedScreenOrientation = requestedOrientation,
+ )
- val finalSize = Size(taskHeight, taskWidth)
// Use the center x as the resizing anchor point.
- val left = taskBounds.centerX() - finalSize.width / 2
- val right = left + finalSize.width
- val finalBounds = Rect(left, taskBounds.top, right, taskBounds.top + finalSize.height)
+ val left = taskBounds.centerX() - newOrientationBounds.width() / 2
+ val right = left + newOrientationBounds.width()
+ val finalBounds =
+ Rect(left, taskBounds.top, right, taskBounds.top + newOrientationBounds.height())
val wct = WindowContainerTransaction().setBounds(task.token, finalBounds)
resizeHandler.startTransition(wct)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
index 683b74392fa6..3b98f8123b46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -17,16 +17,20 @@
package com.android.wm.shell.desktopmode
import android.content.Context
+import android.view.Display
import android.view.Display.DEFAULT_DISPLAY
import android.window.DesktopExperienceFlags
import com.android.internal.protolog.ProtoLog
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
@@ -36,7 +40,9 @@ class DesktopDisplayEventHandler(
private val context: Context,
shellInit: ShellInit,
private val mainScope: CoroutineScope,
+ private val shellController: ShellController,
private val displayController: DisplayController,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val desktopRepositoryInitializer: DesktopRepositoryInitializer,
private val desktopUserRepositories: DesktopUserRepositories,
private val desktopTasksController: DesktopTasksController,
@@ -53,8 +59,17 @@ class DesktopDisplayEventHandler(
private fun onInit() {
displayController.addDisplayWindowListener(this)
- if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue()) {
+ if (DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) {
desktopTasksController.onDeskRemovedListener = this
+
+ shellController.addUserChangeListener(
+ object : UserChangeListener {
+ override fun onUserChanged(newUserId: Int, userContext: Context) {
+ val displayIds = rootTaskDisplayAreaOrganizer.displayIds
+ createDefaultDesksIfNeeded(displayIds.toSet())
+ }
+ }
+ )
}
}
@@ -63,23 +78,7 @@ class DesktopDisplayEventHandler(
desktopDisplayModeController.refreshDisplayWindowingMode()
}
- if (!supportsDesks(displayId)) {
- logV("Display #$displayId does not support desks")
- return
- }
-
- mainScope.launch {
- desktopRepositoryInitializer.isInitialized.collect { initialized ->
- if (!initialized) return@collect
- if (desktopRepository.getNumberOfDesks(displayId) == 0) {
- logV("Creating new desk in new display#$displayId")
- // TODO: b/393978539 - consider activating the desk on creation when
- // applicable, such as for connected displays.
- desktopTasksController.createDesk(displayId)
- }
- cancel()
- }
- }
+ createDefaultDesksIfNeeded(displayIds = setOf(displayId))
}
override fun onDisplayRemoved(displayId: Int) {
@@ -93,8 +92,34 @@ class DesktopDisplayEventHandler(
override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
val remainingDesks = desktopRepository.getNumberOfDesks(lastDisplayId)
if (remainingDesks == 0) {
- logV("All desks removed from display#$lastDisplayId, creating empty desk")
- desktopTasksController.createDesk(lastDisplayId)
+ logV("All desks removed from display#$lastDisplayId")
+ createDefaultDesksIfNeeded(setOf(lastDisplayId))
+ }
+ }
+
+ private fun createDefaultDesksIfNeeded(displayIds: Set<Int>) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
+ logV("createDefaultDesksIfNeeded displays=%s", displayIds)
+ mainScope.launch {
+ desktopRepositoryInitializer.isInitialized.collect { initialized ->
+ if (!initialized) return@collect
+ displayIds
+ .filter { displayId -> displayId != Display.INVALID_DISPLAY }
+ .filter { displayId -> supportsDesks(displayId) }
+ .filter { displayId -> desktopRepository.getNumberOfDesks(displayId) == 0 }
+ .also { displaysNeedingDesk ->
+ logV(
+ "createDefaultDesksIfNeeded creating default desks in displays=%s",
+ displaysNeedingDesk,
+ )
+ }
+ .forEach { displayId ->
+ // TODO: b/393978539 - consider activating the desk on creation when
+ // applicable, such as for connected displays.
+ desktopTasksController.createDesk(displayId)
+ }
+ cancel()
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt
new file mode 100644
index 000000000000..93ba71a17937
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImeHandler.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.view.SurfaceControl
+import com.android.window.flags.Flags
+import com.android.wm.shell.common.DisplayImeController
+import com.android.wm.shell.common.DisplayImeController.ImePositionProcessor.IME_ANIMATION_DEFAULT
+import com.android.wm.shell.sysui.ShellInit
+
+/** Handles the interactions between IME and desktop tasks */
+class DesktopImeHandler(
+ private val displayImeController: DisplayImeController,
+ shellInit: ShellInit,
+) : DisplayImeController.ImePositionProcessor {
+
+ init {
+ shellInit.addInitCallback(::onInit, this)
+ }
+
+ private fun onInit() {
+ if (Flags.enableDesktopImeBugfix()) {
+ displayImeController.addPositionProcessor(this)
+ }
+ }
+
+ override fun onImeStartPositioning(
+ displayId: Int,
+ hiddenTop: Int,
+ shownTop: Int,
+ showing: Boolean,
+ isFloating: Boolean,
+ t: SurfaceControl.Transaction?,
+ ): Int {
+ return IME_ANIMATION_DEFAULT
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
index 1ea545f3ab67..19507c17bc95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandler.kt
@@ -23,10 +23,7 @@ import android.hardware.input.InputManager
import android.hardware.input.InputManager.KeyGestureEventHandler
import android.hardware.input.KeyGestureEvent
import android.os.IBinder
-import android.window.DesktopModeFlags
-import com.android.hardware.input.Flags.manageKeyGestures
import com.android.internal.protolog.ProtoLog
-import com.android.window.flags.Flags.enableMoveToNextDisplayShortcut
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ShellExecutor
@@ -51,16 +48,20 @@ class DesktopModeKeyGestureHandler(
) : KeyGestureEventHandler {
init {
- inputManager.registerKeyGestureEventHandler(this)
+ if (desktopTasksController.isPresent && desktopModeWindowDecorViewModel.isPresent) {
+ val supportedGestures =
+ listOf(
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW,
+ )
+ inputManager.registerKeyGestureEventHandler(supportedGestures, this)
+ }
}
- override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?): Boolean {
- if (
- !desktopTasksController.isPresent ||
- !desktopModeWindowDecorViewModel.isPresent
- ) {
- return false
- }
+ override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) {
when (event.keyGestureType) {
KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY -> {
logV("Key gesture MOVE_TO_NEXT_DISPLAY is handled")
@@ -69,7 +70,6 @@ class DesktopModeKeyGestureHandler(
desktopTasksController.get().moveToNextDisplay(it.taskId)
}
}
- return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW -> {
logV("Key gesture SNAP_LEFT_FREEFORM_WINDOW is handled")
@@ -85,7 +85,6 @@ class DesktopModeKeyGestureHandler(
)
}
}
- return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW -> {
logV("Key gesture SNAP_RIGHT_FREEFORM_WINDOW is handled")
@@ -101,7 +100,6 @@ class DesktopModeKeyGestureHandler(
)
}
}
- return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture TOGGLE_MAXIMIZE_FREEFORM_WINDOW is handled")
@@ -120,7 +118,6 @@ class DesktopModeKeyGestureHandler(
)
}
}
- return true
}
KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW -> {
logV("Key gesture MINIMIZE_FREEFORM_WINDOW is handled")
@@ -129,9 +126,7 @@ class DesktopModeKeyGestureHandler(
desktopTasksController.get().minimizeTask(it, MinimizeReason.KEY_GESTURE)
}
}
- return true
}
- else -> return false
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
index b9cb32d8a14f..e0300d688379 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLogger.kt
@@ -167,7 +167,35 @@ class DesktopModeUiEventLogger(
@UiEvent(doc = "Exit desktop mode education tooltip on the app header menu is clicked")
EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_CLICKED(2104),
@UiEvent(doc = "Exit desktop mode education tooltip is dismissed by the user")
- EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2105);
+ EXIT_DESKTOP_MODE_EDUCATION_TOOLTIP_DISMISSED(2105),
+ @UiEvent(doc = "A11y service opened app handle menu by selecting handle from fullscreen")
+ A11Y_APP_HANDLE_MENU_OPENED(2156),
+ @UiEvent(doc = "A11y service opened app handle menu through Switch Access actions menu ")
+ A11Y_SYSTEM_ACTION_APP_HANDLE_MENU(2157),
+ @UiEvent(doc = "A11y service selected desktop mode from app handle menu")
+ A11Y_APP_HANDLE_MENU_DESKTOP_VIEW(2158),
+ @UiEvent(doc = "A11y service selected fullscreen mode from app handle menu")
+ A11Y_APP_HANDLE_MENU_FULLSCREEN(2159),
+ @UiEvent(doc = "A11y service selected split screen mode from app handle menu")
+ A11Y_APP_HANDLE_MENU_SPLIT_SCREEN(2160),
+ @UiEvent(doc = "A11y service selected maximize/restore button from app header")
+ A11Y_APP_WINDOW_MAXIMIZE_RESTORE_BUTTON(2161),
+ @UiEvent(doc = "A11y service selected minimize button from app header")
+ A11Y_APP_WINDOW_MINIMIZE_BUTTON(2162),
+ @UiEvent(doc = "A11y service selected close button from app header")
+ A11Y_APP_WINDOW_CLOSE_BUTTON(2163),
+ @UiEvent(doc = "A11y service selected maximize button from app header maximize menu")
+ A11Y_MAXIMIZE_MENU_MAXIMIZE(2164),
+ @UiEvent(doc = "A11y service selected resize left button from app header maximize menu")
+ A11Y_MAXIMIZE_MENU_RESIZE_LEFT(2165),
+ @UiEvent(doc = "A11y service selected resize right button from app header maximize menu")
+ A11Y_MAXIMIZE_MENU_RESIZE_RIGHT(2166),
+ @UiEvent(doc = "A11y service triggered a11y action to maximize/restore app window")
+ A11Y_ACTION_MAXIMIZE_RESTORE(2167),
+ @UiEvent(doc = "A11y service triggered a11y action to resize app window left")
+ A11Y_ACTION_RESIZE_LEFT(2168),
+ @UiEvent(doc = "A11y service triggered a11y action to resize app window right")
+ A11Y_ACTION_RESIZE_RIGHT(2169);
override fun getId(): Int = mId
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index a8b0bafee724..3c44fe8061aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -69,6 +69,7 @@ fun calculateInitialBounds(
taskInfo: RunningTaskInfo,
scale: Float = DESKTOP_MODE_INITIAL_BOUNDS_SCALE,
captionInsets: Int = 0,
+ requestedScreenOrientation: Int? = null,
): Rect {
val screenBounds = Rect(0, 0, displayLayout.width(), displayLayout.height())
val appAspectRatio = calculateAspectRatio(taskInfo)
@@ -85,12 +86,13 @@ fun calculateInitialBounds(
}
val topActivityInfo =
taskInfo.topActivityInfo ?: return positionInScreen(idealSize, stableBounds)
+ val screenOrientation = requestedScreenOrientation ?: topActivityInfo.screenOrientation
val initialSize: Size =
when (taskInfo.configuration.orientation) {
ORIENTATION_LANDSCAPE -> {
if (taskInfo.canChangeAspectRatio) {
- if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
+ if (isFixedOrientationPortrait(screenOrientation)) {
// For portrait resizeable activities, respect apps fullscreen width but
// apply ideal size height.
Size(
@@ -104,14 +106,20 @@ fun calculateInitialBounds(
} else {
// If activity is unresizeable, regardless of orientation, calculate maximum
// size (within the ideal size) maintaining original aspect ratio.
- maximizeSizeGivenAspectRatio(taskInfo, idealSize, appAspectRatio, captionInsets)
+ maximizeSizeGivenAspectRatio(
+ taskInfo,
+ idealSize,
+ appAspectRatio,
+ captionInsets,
+ screenOrientation,
+ )
}
}
ORIENTATION_PORTRAIT -> {
val customPortraitWidthForLandscapeApp =
screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
if (taskInfo.canChangeAspectRatio) {
- if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ if (isFixedOrientationLandscape(screenOrientation)) {
// For landscape resizeable activities, respect apps fullscreen height and
// apply custom app width.
Size(
@@ -123,7 +131,7 @@ fun calculateInitialBounds(
idealSize
}
} else {
- if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
+ if (isFixedOrientationLandscape(screenOrientation)) {
// For landscape unresizeable activities, apply custom app width to ideal
// size and calculate maximum size with this area while maintaining original
// aspect ratio.
@@ -132,6 +140,7 @@ fun calculateInitialBounds(
Size(customPortraitWidthForLandscapeApp, idealSize.height),
appAspectRatio,
captionInsets,
+ screenOrientation,
)
} else {
// For portrait unresizeable activities, calculate maximum size (within the
@@ -141,6 +150,7 @@ fun calculateInitialBounds(
idealSize,
appAspectRatio,
captionInsets,
+ screenOrientation,
)
}
}
@@ -190,13 +200,16 @@ fun maximizeSizeGivenAspectRatio(
targetArea: Size,
aspectRatio: Float,
captionInsets: Int = 0,
+ requestedScreenOrientation: Int? = null,
): Size {
val targetHeight = targetArea.height - captionInsets
val targetWidth = targetArea.width
val finalHeight: Int
val finalWidth: Int
// Get orientation either through top activity or task's orientation
- if (taskInfo.hasPortraitTopActivity()) {
+ val screenOrientation =
+ requestedScreenOrientation ?: taskInfo.topActivityInfo?.screenOrientation
+ if (taskInfo.hasPortraitTopActivity(screenOrientation)) {
val tempWidth = ceil(targetHeight / aspectRatio).toInt()
if (tempWidth <= targetWidth) {
finalHeight = targetHeight
@@ -354,9 +367,8 @@ fun centerInArea(desiredSize: Size, areaBounds: Rect, leftStart: Int, topStart:
return Rect(newLeft, newTop, newRight, newBottom)
}
-private fun TaskInfo.hasPortraitTopActivity(): Boolean {
- val topActivityScreenOrientation =
- topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
+private fun TaskInfo.hasPortraitTopActivity(screenOrientation: Int?): Boolean {
+ val topActivityScreenOrientation = screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
val appBounds = configuration.windowConfiguration.appBounds
return when {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index e77acfb805a6..25fdbb348356 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -1112,6 +1112,7 @@ class DesktopRepository(
internal fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopRepository")
+ pw.println("${innerPrefix}userId=$userId")
dumpDesktopTaskData(pw, innerPrefix)
pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
@@ -1298,6 +1299,7 @@ class DesktopRepository(
deskByDisplayId[displayId]?.let { sequenceOf(it) } ?: emptySequence()
override fun remove(deskId: Int) {
+ setDeskInactive(deskId)
deskByDisplayId[deskId]?.clear()
}
@@ -1397,6 +1399,7 @@ class DesktopRepository(
desktopDisplays[displayId]?.orderedDesks?.asSequence() ?: emptySequence()
override fun remove(deskId: Int) {
+ setDeskInactive(deskId)
desktopDisplays.forEach { _, display ->
display.orderedDesks.removeIf { it.deskId == deskId }
}
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 093e8ef8bc0e..5849d4af4e7e 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
@@ -465,6 +465,30 @@ class DesktopTasksController(
return isFreeformDisplay
}
+ /** Called when the recents transition that started while in desktop is finishing. */
+ fun onRecentsInDesktopAnimationFinishing(
+ transition: IBinder,
+ finishWct: WindowContainerTransaction,
+ returnToApp: Boolean,
+ ) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
+ logV("onRecentsInDesktopAnimationFinishing returnToApp=%b", returnToApp)
+ if (returnToApp) return
+ // Home/Recents only exists in the default display.
+ val activeDesk = taskRepository.getActiveDeskId(DEFAULT_DISPLAY) ?: return
+ // Not going back to the active desk, deactivate it.
+ val runOnTransitStart =
+ performDesktopExitCleanUp(
+ wct = finishWct,
+ deskId = activeDesk,
+ displayId = DEFAULT_DISPLAY,
+ willExitDesktop = true,
+ shouldEndUpAtHome = true,
+ fromRecentsTransition = true,
+ )
+ runOnTransitStart?.invoke(transition)
+ }
+
/** Creates a new desk in the given display. */
fun createDesk(displayId: Int) {
if (displayId == Display.INVALID_DISPLAY) {
@@ -835,20 +859,22 @@ class DesktopTasksController(
val requestRes = transitions.dispatchRequest(Binder(), requestInfo, /* skip= */ null)
wct.merge(requestRes.second, true)
- desktopPipTransitionObserver.get().addPendingPipTransition(
- DesktopPipTransitionObserver.PendingPipTransition(
- token = freeformTaskTransitionStarter.startPipTransition(wct),
- taskId = taskInfo.taskId,
- onSuccess = {
- onDesktopTaskEnteredPip(
- taskId = taskId,
- deskId = deskId,
- displayId = taskInfo.displayId,
- taskIsLastVisibleTaskBeforePip = isLastTask,
- )
- },
+ desktopPipTransitionObserver
+ .get()
+ .addPendingPipTransition(
+ DesktopPipTransitionObserver.PendingPipTransition(
+ token = freeformTaskTransitionStarter.startPipTransition(wct),
+ taskId = taskInfo.taskId,
+ onSuccess = {
+ onDesktopTaskEnteredPip(
+ taskId = taskId,
+ deskId = deskId,
+ displayId = taskInfo.displayId,
+ taskIsLastVisibleTaskBeforePip = isLastTask,
+ )
+ },
+ )
)
- )
} else {
snapEventHandler.removeTaskIfTiled(displayId, taskId)
val willExitDesktop = willExitDesktop(taskId, displayId, forceExitDesktop = false)
@@ -1208,6 +1234,7 @@ class DesktopTasksController(
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
launchBounds = bounds
+ launchDisplayId = displayId
if (DesktopModeFlags.ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX.isTrue) {
// Sets launch bounds size as flexible so core can recalculate.
flexibleLaunchSize = true
@@ -1937,16 +1964,23 @@ class DesktopTasksController(
displayId: Int,
willExitDesktop: Boolean,
shouldEndUpAtHome: Boolean = true,
+ fromRecentsTransition: Boolean = false,
): RunOnTransitStart? {
if (!willExitDesktop) return null
desktopModeEnterExitTransitionListener?.onExitDesktopModeTransitionStarted(
FULLSCREEN_ANIMATION_DURATION
)
- removeWallpaperActivity(wct, displayId)
- if (shouldEndUpAtHome) {
- // If the transition should end up with user going to home, launch home with a pending
- // intent.
- addLaunchHomePendingIntent(wct, displayId)
+ // No need to clean up the wallpaper / reorder home when coming from a recents transition.
+ if (
+ !fromRecentsTransition ||
+ !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
+ ) {
+ removeWallpaperActivity(wct, displayId)
+ if (shouldEndUpAtHome) {
+ // If the transition should end up with user going to home, launch home with a
+ // pending intent.
+ addLaunchHomePendingIntent(wct, displayId)
+ }
}
return prepareDeskDeactivationIfNeeded(wct, deskId)
}
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 c10752d36bf9..72758bd538d6 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
@@ -21,6 +21,7 @@ import android.content.Context
import android.content.pm.UserInfo
import android.os.UserManager
import android.util.SparseArray
+import android.window.DesktopExperienceFlags
import android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_HSUM
import androidx.core.util.forEach
import com.android.internal.protolog.ProtoLog
@@ -68,7 +69,10 @@ class DesktopUserRepositories(
if (DesktopModeStatus.canEnterDesktopMode(context)) {
shellInit.addInitCallback(::onInit, this)
}
- if (ENABLE_DESKTOP_WINDOWING_HSUM.isTrue()) {
+ if (
+ ENABLE_DESKTOP_WINDOWING_HSUM.isTrue() &&
+ !DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
+ ) {
userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
}
}
@@ -76,6 +80,13 @@ class DesktopUserRepositories(
private fun onInit() {
repositoryInitializer.initialize(this)
shellController.addUserChangeListener(this)
+ if (
+ ENABLE_DESKTOP_WINDOWING_HSUM.isTrue() &&
+ DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue
+ ) {
+ userId = ActivityManager.getCurrentUser()
+ userIdToProfileIdsMap[userId] = userManager.getProfiles(userId).map { it.id }
+ }
}
/** Returns [DesktopRepository] for the parent user id. */
@@ -97,10 +108,10 @@ class DesktopUserRepositories(
/** Dumps [DesktopRepository] for each user. */
fun dump(pw: PrintWriter, prefix: String) {
- desktopRepoByUserId.forEach { key, value ->
- pw.println("${prefix}userId=$key")
- value.dump(pw, prefix)
- }
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopUserRepositories:")
+ pw.println("${innerPrefix}currentUserId=$userId")
+ desktopRepoByUserId.forEach { key, value -> value.dump(pw, innerPrefix) }
}
override fun onUserChanged(newUserId: Int, userContext: Context) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
index 8ce624e103ef..bf687f2e4f3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/common/DefaultHomePackageSupplier.kt
@@ -20,6 +20,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.pm.PackageManager
import android.os.Handler
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.sysui.ShellInit
@@ -28,6 +29,7 @@ import java.util.function.Supplier
/**
* This supplies the package name of default home in an efficient way. The query to package manager
* only executes on initialization and when the preferred activity (e.g. default home) is changed.
+ * Note that this returns null package name if the default home is setup wizard.
*/
class DefaultHomePackageSupplier(
private val context: Context,
@@ -36,6 +38,7 @@ class DefaultHomePackageSupplier(
) : BroadcastReceiver(), Supplier<String?> {
private var defaultHomePackage: String? = null
+ private var isSetupWizard: Boolean = false
init {
shellInit.addInitCallback({ onInit() }, this)
@@ -52,6 +55,14 @@ class DefaultHomePackageSupplier(
private fun updateDefaultHomePackage(): String? {
defaultHomePackage = context.packageManager.getHomeActivities(ArrayList())?.packageName
+ isSetupWizard =
+ defaultHomePackage != null &&
+ context.packageManager.resolveActivity(
+ Intent()
+ .setPackage(defaultHomePackage)
+ .addCategory(Intent.CATEGORY_SETUP_WIZARD),
+ PackageManager.MATCH_SYSTEM_ONLY,
+ ) != null
return defaultHomePackage
}
@@ -60,6 +71,7 @@ class DefaultHomePackageSupplier(
}
override fun get(): String? {
+ if (isSetupWizard) return null
return defaultHomePackage ?: updateDefaultHomePackage()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
index 9dec96933ee5..454419c805c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -18,9 +18,12 @@ package com.android.wm.shell.desktopmode.multidesks
import android.os.IBinder
/** Represents shell-started transitions involving desks. */
-sealed class DeskTransition {
+sealed interface DeskTransition {
/** The transition token. */
- abstract val token: IBinder
+ val token: IBinder
+
+ /** Returns a copy of this desk transition with a new transition token. */
+ fun copyWithToken(token: IBinder): DeskTransition
/** A transition to remove a desk and its tasks from a display. */
data class RemoveDesk(
@@ -29,11 +32,15 @@ sealed class DeskTransition {
val deskId: Int,
val tasks: Set<Int>,
val onDeskRemovedListener: OnDeskRemovedListener?,
- ) : DeskTransition()
+ ) : DeskTransition {
+ override fun copyWithToken(token: IBinder): DeskTransition = copy(token)
+ }
/** A transition to activate a desk in its display. */
data class ActivateDesk(override val token: IBinder, val displayId: Int, val deskId: Int) :
- DeskTransition()
+ DeskTransition {
+ override fun copyWithToken(token: IBinder): DeskTransition = copy(token)
+ }
/** A transition to activate a desk by moving an outside task to it. */
data class ActiveDeskWithTask(
@@ -41,8 +48,12 @@ sealed class DeskTransition {
val displayId: Int,
val deskId: Int,
val enterTaskId: Int,
- ) : DeskTransition()
+ ) : DeskTransition {
+ override fun copyWithToken(token: IBinder): DeskTransition = copy(token)
+ }
/** A transition to deactivate a desk. */
- data class DeactivateDesk(override val token: IBinder, val deskId: Int) : DeskTransition()
+ data class DeactivateDesk(override val token: IBinder, val deskId: Int) : DeskTransition {
+ override fun copyWithToken(token: IBinder): DeskTransition = copy(token)
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
index b521b2e8c942..588b5c350330 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -39,6 +39,7 @@ class DesksTransitionObserver(
val transitions = deskTransitions[transition.token] ?: mutableSetOf()
transitions += transition
deskTransitions[transition.token] = transitions
+ logD("Added pending desk transition: %s", transition)
}
/**
@@ -51,6 +52,43 @@ class DesksTransitionObserver(
deskTransitions.forEach { deskTransition -> handleDeskTransition(info, deskTransition) }
}
+ /**
+ * Called when a transition is merged with another transition, which may include transitions not
+ * tracked by this observer.
+ */
+ fun onTransitionMerged(merged: IBinder, playing: IBinder) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
+ val transitions = deskTransitions.remove(merged) ?: return
+ deskTransitions[playing] =
+ transitions
+ .map { deskTransition -> deskTransition.copyWithToken(token = playing) }
+ .toMutableSet()
+ }
+
+ /**
+ * Called when any transition finishes, which may include transitions not tracked by this
+ * observer.
+ *
+ * Most [DeskTransition]s are not handled here because [onTransitionReady] handles them and
+ * removes them from the map. However, there can be cases where the transition was added after
+ * [onTransitionReady] had already been called and they need to be handled here, such as the
+ * swipe-to-home recents transition when there is no book-end transition.
+ */
+ fun onTransitionFinished(transition: IBinder) {
+ if (!DesktopExperienceFlags.ENABLE_MULTIPLE_DESKTOPS_BACKEND.isTrue) return
+ val deskTransitions = deskTransitions.remove(transition) ?: return
+ deskTransitions.forEach { deskTransition ->
+ if (deskTransition is DeskTransition.DeactivateDesk) {
+ handleDeactivateDeskTransition(null, deskTransition)
+ } else {
+ logW(
+ "Unexpected desk transition finished without being handled: %s",
+ deskTransition,
+ )
+ }
+ }
+ }
+
private fun handleDeskTransition(info: TransitionInfo, deskTransition: DeskTransition) {
logD("Desk transition ready: %s", deskTransition)
val desktopRepository = desktopUserRepositories.current
@@ -102,41 +140,54 @@ class DesksTransitionObserver(
)
}
}
- is DeskTransition.DeactivateDesk -> {
- var visibleDeactivation = false
- for (change in info.changes) {
- val isDeskChange = desksOrganizer.isDeskChange(change, deskTransition.deskId)
- if (isDeskChange) {
- visibleDeactivation = true
- continue
- }
- val taskId = change.taskInfo?.taskId ?: continue
- val removedFromDesk =
- desktopRepository.getDeskIdForTask(taskId) == deskTransition.deskId &&
- desksOrganizer.getDeskAtEnd(change) == null
- if (removedFromDesk) {
- desktopRepository.removeTaskFromDesk(
- deskId = deskTransition.deskId,
- taskId = taskId,
- )
- }
- }
- // Always deactivate even if there's no change that confirms the desk was
- // deactivated. Some interactions, such as the desk deactivating because it's
- // occluded by a fullscreen task result in a transition change, but others, such
- // as transitioning from an empty desk to home may not.
- if (!visibleDeactivation) {
- logD("Deactivating desk without transition change")
- }
- desktopRepository.setDeskInactive(deskId = deskTransition.deskId)
+ is DeskTransition.DeactivateDesk -> handleDeactivateDeskTransition(info, deskTransition)
+ }
+ }
+
+ private fun handleDeactivateDeskTransition(
+ info: TransitionInfo?,
+ deskTransition: DeskTransition.DeactivateDesk,
+ ) {
+ logD("handleDeactivateDeskTransition: %s", deskTransition)
+ val desktopRepository = desktopUserRepositories.current
+ var deskChangeFound = false
+
+ val changes = info?.changes ?: emptyList()
+ for (change in changes) {
+ val isDeskChange = desksOrganizer.isDeskChange(change, deskTransition.deskId)
+ if (isDeskChange) {
+ deskChangeFound = true
+ continue
+ }
+ val taskId = change.taskInfo?.taskId ?: continue
+ val removedFromDesk =
+ desktopRepository.getDeskIdForTask(taskId) == deskTransition.deskId &&
+ desksOrganizer.getDeskAtEnd(change) == null
+ if (removedFromDesk) {
+ desktopRepository.removeTaskFromDesk(
+ deskId = deskTransition.deskId,
+ taskId = taskId,
+ )
}
}
+ // Always deactivate even if there's no change that confirms the desk was
+ // deactivated. Some interactions, such as the desk deactivating because it's
+ // occluded by a fullscreen task result in a transition change, but others, such
+ // as transitioning from an empty desk to home may not.
+ if (!deskChangeFound) {
+ logD("Deactivating desk without transition change")
+ }
+ desktopRepository.setDeskInactive(deskId = deskTransition.deskId)
}
private fun logD(msg: String, vararg arguments: Any?) {
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
+ private fun logW(msg: String, vararg arguments: Any?) {
+ ProtoLog.w(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
+ }
+
private companion object {
private const val TAG = "DesksTransitionObserver"
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index b3c1a92f5e1d..d6084138b7e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -663,6 +663,9 @@ public class DragLayout extends LinearLayout
mSession = null;
}
});
+ // notify bubbles of drag cancel
+ mCurrentBubbleBarTarget = null;
+ mBubbleBarDragListener.onItemDraggedOutsideBubbleBarDropZone();
// Reset the state if we previously force-ignore the bottom margin
mDropZoneView1.setForceIgnoreBottomMargin(false);
mDropZoneView2.setForceIgnoreBottomMargin(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
index 0bf2ea61b0a4..e7492f17835a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserver.java
@@ -207,6 +207,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionMerged(@NonNull IBinder merged, @NonNull IBinder playing) {
+ mDesksTransitionObserver.ifPresent(o -> o.onTransitionMerged(merged, playing));
if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
// TODO(b/367268953): Remove when DesktopTaskListener is introduced.
mDesktopImmersiveController.ifPresent(h -> h.onTransitionMerged(merged, playing));
@@ -232,6 +233,7 @@ public class FreeformTaskTransitionObserver implements Transitions.TransitionObs
@Override
public void onTransitionFinished(@NonNull IBinder transition, boolean aborted) {
+ mDesksTransitionObserver.ifPresent(o -> o.onTransitionFinished(transition));
if (DesktopModeFlags.ENABLE_FULLY_IMMERSIVE_IN_DESKTOP.isTrue()) {
// TODO(b/367268953): Remove when DesktopTaskListener is introduced.
mDesktopImmersiveController.ifPresent(h -> h.onTransitionFinished(transition, aborted));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index a0fb62508cc1..e19ad23b2125 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -394,6 +394,9 @@ public class DefaultMixedHandler implements MixedTransitionHandler,
if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_SPLIT) {
((RecentsMixedTransition) mixed).onAnimateRecentsDuringSplitFinishing(
returnToApp, finishWct, finishT);
+ } else if (mixed.mType == MixedTransition.TYPE_RECENTS_DURING_DESKTOP) {
+ ((RecentsMixedTransition) mixed).onAnimateRecentsDuringDesktopFinishing(
+ returnToApp, finishWct);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index bf5800330979..825e8c70fa77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -59,7 +59,6 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
import static com.android.internal.jank.Cuj.CUJ_DEFAULT_TASK_TO_TASK_ANIMATION;
-import static com.android.internal.policy.TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
@@ -116,6 +115,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.sysui.ShellInit;
import java.util.ArrayList;
@@ -125,6 +125,7 @@ import java.util.function.Consumer;
/** The default handler that handles anything not already handled. */
public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static final int MAX_ANIMATION_DURATION = 3000;
+ private static final int SIZE_CHANGE_ANIMATION_DURATION = 400;
private final TransactionPool mTransactionPool;
private final DisplayController mDisplayController;
@@ -779,15 +780,16 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private void startBoundsChangeAnimation(@NonNull SurfaceControl.Transaction startT,
@NonNull ArrayList<Animator> animations, @NonNull TransitionInfo.Change change,
@NonNull Runnable finishCb, @NonNull ShellExecutor mainExecutor) {
- final SizeChangeAnimation sca =
- new SizeChangeAnimation(change.getStartAbsBounds(), change.getEndAbsBounds());
+ final SizeChangeAnimation sca = new SizeChangeAnimation(change.getStartAbsBounds(),
+ change.getEndAbsBounds(), /* initialScale= */ 1f, /* scaleFactor= */ 1f);
sca.initialize(change.getLeash(), change.getSnapshot(), startT);
final ValueAnimator va = sca.buildAnimator(change.getLeash(), change.getSnapshot(),
(animator) -> mainExecutor.execute(() -> {
animations.remove(animator);
finishCb.run();
}));
- va.setDuration(DEFAULT_APP_TRANSITION_DURATION);
+ va.setDuration(SIZE_CHANGE_ANIMATION_DURATION);
+ va.setInterpolator(Interpolators.EMPHASIZED);
animations.add(va);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 1e926c57ca61..cfefc1f1ac66 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -198,6 +198,15 @@ class RecentsMixedTransition extends DefaultMixedHandler.MixedTransition {
}
}
+ /**
+ * Called when the recents animation during desktop is about to finish.
+ */
+ void onAnimateRecentsDuringDesktopFinishing(boolean returnToApp,
+ @NonNull WindowContainerTransaction finishWct) {
+ mDesktopTasksController.onRecentsInDesktopAnimationFinishing(mTransition, finishWct,
+ returnToApp);
+ }
+
@Override
void mergeAnimation(
@NonNull IBinder transition, @NonNull TransitionInfo info,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 800faca830f4..69e1f36dec0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -108,6 +108,8 @@ import com.android.wm.shell.common.MultiInstanceHelper;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController;
+import com.android.wm.shell.compatui.api.CompatUIHandler;
+import com.android.wm.shell.compatui.impl.CompatUIRequests;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
@@ -266,6 +268,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
private final DesktopTilingDecorViewModel mDesktopTilingDecorViewModel;
private final MultiDisplayDragMoveIndicatorController mMultiDisplayDragMoveIndicatorController;
private final LatencyTracker mLatencyTracker;
+ private final CompatUIHandler mCompatUI;
public DesktopModeWindowDecorViewModel(
Context context,
@@ -306,7 +309,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
- MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
+ CompatUIHandler compatUI) {
this(
context,
shellExecutor,
@@ -353,7 +357,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
recentsTransitionHandler,
desktopModeCompatPolicy,
desktopTilingDecorViewModel,
- multiDisplayDragMoveIndicatorController);
+ multiDisplayDragMoveIndicatorController,
+ compatUI);
}
@VisibleForTesting
@@ -403,7 +408,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
RecentsTransitionHandler recentsTransitionHandler,
DesktopModeCompatPolicy desktopModeCompatPolicy,
DesktopTilingDecorViewModel desktopTilingDecorViewModel,
- MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController) {
+ MultiDisplayDragMoveIndicatorController multiDisplayDragMoveIndicatorController,
+ CompatUIHandler compatUI) {
mContext = context;
mMainExecutor = shellExecutor;
mMainHandler = mainHandler;
@@ -444,6 +450,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mActivityOrientationChangeHandler = activityOrientationChangeHandler;
mAssistContentRequester = assistContentRequester;
mWindowDecorViewHostSupplier = windowDecorViewHostSupplier;
+ mCompatUI = compatUI;
mOnDisplayChangingListener = (displayId, fromRotation, toRotation, displayAreaInfo, t) -> {
DesktopModeWindowDecoration decoration;
RunningTaskInfo taskInfo;
@@ -1795,6 +1802,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mMultiInstanceHelper,
mWindowDecorCaptionHandleRepository,
mDesktopModeEventLogger,
+ mDesktopModeUiEventLogger,
mDesktopModeCompatPolicy);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
@@ -1864,6 +1872,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
CompatUIController.launchUserAspectRatioSettings(mContext, taskInfo);
return Unit.INSTANCE;
});
+ windowDecoration.setOnRestartClickListener(() -> {
+ mCompatUI.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog(
+ taskInfo.taskId));
+ return Unit.INSTANCE;
+ });
windowDecoration.setOnMaximizeHoverListener(() -> {
if (!windowDecoration.isMaximizeMenuActive()) {
mDesktopModeUiEventLogger.log(taskInfo,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index ae103895d56b..50bc7b5e865b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -93,6 +93,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopModeUtils;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
@@ -163,6 +164,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private Function0<Unit> mOnNewWindowClickListener;
private Function0<Unit> mOnManageWindowsClickListener;
private Function0<Unit> mOnChangeAspectRatioClickListener;
+ private Function0<Unit> mOnRestartClickListener;
private Function0<Unit> mOnMaximizeHoverListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
@@ -210,6 +212,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private final MultiInstanceHelper mMultiInstanceHelper;
private final WindowDecorCaptionHandleRepository mWindowDecorCaptionHandleRepository;
private final DesktopUserRepositories mDesktopUserRepositories;
+ private final DesktopModeUiEventLogger mDesktopModeUiEventLogger;
private boolean mIsRecentsTransitionRunning = false;
private boolean mIsDragging = false;
private Runnable mLoadAppInfoRunnable;
@@ -242,6 +245,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
this (context, userContext, displayController, taskResourceLoader, splitScreenController,
desktopUserRepositories, taskOrganizer, taskInfo, taskSurface, handler,
@@ -256,7 +260,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
DefaultMaximizeMenuFactory.INSTANCE,
DefaultHandleMenuFactory.INSTANCE, multiInstanceHelper,
windowDecorCaptionHandleRepository, desktopModeEventLogger,
- desktopModeCompatPolicy);
+ desktopModeUiEventLogger, desktopModeCompatPolicy);
}
DesktopModeWindowDecoration(
@@ -293,6 +297,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
super(context, userContext, displayController, taskOrganizer, taskInfo,
taskSurface, surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
@@ -320,6 +325,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mTaskResourceLoader = taskResourceLoader;
mTaskResourceLoader.onWindowDecorCreated(taskInfo);
mDesktopModeCompatPolicy = desktopModeCompatPolicy;
+ mDesktopModeUiEventLogger = desktopModeUiEventLogger;
}
/**
@@ -408,6 +414,11 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnChangeAspectRatioClickListener = listener;
}
+ /** Registers a listener to be called when the aspect ratio action is triggered. */
+ void setOnRestartClickListener(Function0<Unit> listener) {
+ mOnRestartClickListener = listener;
+ }
+
/** Registers a listener to be called when the maximize header button is hovered. */
void setOnMaximizeHoverListener(Function0<Unit> listener) {
mOnMaximizeHoverListener = listener;
@@ -889,10 +900,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnCaptionTouchListener,
mOnCaptionButtonClickListener,
mWindowManagerWrapper,
- mHandler
+ mHandler,
+ mDesktopModeUiEventLogger
);
- } else if (mRelayoutParams.mLayoutResId
- == R.layout.desktop_mode_app_header) {
+ } else if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_header) {
return mAppHeaderViewHolderFactory.create(
mResult.mRootView,
mOnCaptionTouchListener,
@@ -902,7 +913,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mOnLeftSnapClickListener,
mOnRightSnapClickListener,
mOnMaximizeOrRestoreClickListener,
- mOnMaximizeHoverListener);
+ mOnMaximizeHoverListener,
+ mDesktopModeUiEventLogger);
}
throw new IllegalArgumentException("Unexpected layout resource id");
}
@@ -1366,7 +1378,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
mDisplayController, mTaskInfo, mContext,
(width, height) -> calculateMaximizeMenuPosition(width, height),
- mSurfaceControlTransactionSupplier);
+ mSurfaceControlTransactionSupplier, mDesktopModeUiEventLogger);
mMaximizeMenu.show(
/* isTaskInImmersiveMode= */
@@ -1461,6 +1473,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
&& mMinimumInstancesFound;
final boolean shouldShowChangeAspectRatioButton = HandleMenu.Companion
.shouldShowChangeAspectRatioButton(mTaskInfo);
+ final boolean shouldShowRestartButton = HandleMenu.Companion
+ .shouldShowRestartButton(mTaskInfo);
final boolean inDesktopImmersive = mDesktopUserRepositories.getProfile(mTaskInfo.userId)
.isTaskInFullImmersiveState(mTaskInfo.taskId);
final boolean isBrowserApp = isBrowserApp();
@@ -1477,8 +1491,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
isDesktopModeSupportedOnDisplay(mContext, mDisplay),
+ shouldShowRestartButton,
isBrowserApp,
isBrowserApp ? getAppLink() : getBrowserLink(),
+ mDesktopModeUiEventLogger,
mResult.mCaptionWidth,
mResult.mCaptionHeight,
mResult.mCaptionX,
@@ -1513,6 +1529,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
}
return Unit.INSTANCE;
},
+ /* onRestartClickListener= */ mOnRestartClickListener,
/* onCloseMenuClickListener= */ () -> {
closeHandleMenu();
return Unit.INSTANCE;
@@ -1963,6 +1980,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
MultiInstanceHelper multiInstanceHelper,
WindowDecorCaptionHandleRepository windowDecorCaptionHandleRepository,
DesktopModeEventLogger desktopModeEventLogger,
+ DesktopModeUiEventLogger desktopModeUiEventLogger,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopModeWindowDecoration(
context,
@@ -1990,6 +2008,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
multiInstanceHelper,
windowDecorCaptionHandleRepository,
desktopModeEventLogger,
+ desktopModeUiEventLogger,
desktopModeCompatPolicy);
}
}
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 2d44395b1340..25cf06b4247c 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
@@ -28,6 +28,7 @@ import android.graphics.Bitmap
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
+import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_OUTSIDE
@@ -35,6 +36,7 @@ import android.view.SurfaceControl
import android.view.View
import android.view.WindowInsets.Type.systemBars
import android.view.WindowManager
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.Space
@@ -49,6 +51,10 @@ import androidx.core.view.isGone
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.ContextUtils.isRtl
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_DESKTOP_VIEW
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_FULLSCREEN
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_SPLIT_SCREEN
import com.android.wm.shell.shared.annotations.ShellBackgroundThread
import com.android.wm.shell.shared.annotations.ShellMainThread
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
@@ -94,8 +100,10 @@ class HandleMenu(
private val shouldShowManageWindowsButton: Boolean,
private val shouldShowChangeAspectRatioButton: Boolean,
private val shouldShowDesktopModeButton: Boolean,
+ private val shouldShowRestartButton: Boolean,
private val isBrowserApp: Boolean,
private val openInAppOrBrowserIntent: Intent?,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
private val captionWidth: Int,
private val captionHeight: Int,
captionX: Int,
@@ -138,7 +146,8 @@ class HandleMenu(
private val shouldShowMoreActionsPill: Boolean
get() = SHOULD_SHOW_SCREENSHOT_BUTTON || shouldShowNewWindowButton ||
- shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton
+ shouldShowManageWindowsButton || shouldShowChangeAspectRatioButton ||
+ shouldShowRestartButton
private var loadAppInfoJob: Job? = null
@@ -156,6 +165,7 @@ class HandleMenu(
onChangeAspectRatioClickListener: () -> Unit,
openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
+ onRestartClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
forceShowSystemBars: Boolean = false,
@@ -175,6 +185,7 @@ class HandleMenu(
onChangeAspectRatioClickListener = onChangeAspectRatioClickListener,
openInAppOrBrowserClickListener = openInAppOrBrowserClickListener,
onOpenByDefaultClickListener = onOpenByDefaultClickListener,
+ onRestartClickListener = onRestartClickListener,
onCloseMenuClickListener = onCloseMenuClickListener,
onOutsideTouchListener = onOutsideTouchListener,
forceShowSystemBars = forceShowSystemBars,
@@ -197,12 +208,14 @@ class HandleMenu(
onChangeAspectRatioClickListener: () -> Unit,
openInAppOrBrowserClickListener: (Intent) -> Unit,
onOpenByDefaultClickListener: () -> Unit,
+ onRestartClickListener: () -> Unit,
onCloseMenuClickListener: () -> Unit,
onOutsideTouchListener: () -> Unit,
forceShowSystemBars: Boolean = false,
) {
val handleMenuView = HandleMenuView(
context = context,
+ desktopModeUiEventLogger = desktopModeUiEventLogger,
menuWidth = menuWidth,
captionHeight = captionHeight,
shouldShowWindowingPill = shouldShowWindowingPill,
@@ -211,6 +224,7 @@ class HandleMenu(
shouldShowManageWindowsButton = shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton = shouldShowChangeAspectRatioButton,
shouldShowDesktopModeButton = shouldShowDesktopModeButton,
+ shouldShowRestartButton = shouldShowRestartButton,
isBrowserApp = isBrowserApp
).apply {
bind(taskInfo, shouldShowMoreActionsPill)
@@ -224,6 +238,7 @@ class HandleMenu(
this.onOpenInAppOrBrowserClickListener = {
openInAppOrBrowserClickListener.invoke(openInAppOrBrowserIntent!!)
}
+ this.onRestartClickListener = onRestartClickListener
this.onOpenByDefaultClickListener = onOpenByDefaultClickListener
this.onCloseMenuClickListener = onCloseMenuClickListener
this.onOutsideTouchListener = onOutsideTouchListener
@@ -430,6 +445,10 @@ class HandleMenu(
R.dimen.desktop_mode_handle_menu_change_aspect_ratio_height
)
}
+ if (!shouldShowRestartButton) {
+ menuHeight -= loadDimensionPixelSize(
+ R.dimen.desktop_mode_handle_menu_restart_button_height)
+ }
if (!shouldShowMoreActionsPill) {
menuHeight -= pillTopMargin
}
@@ -464,6 +483,7 @@ class HandleMenu(
@SuppressLint("ClickableViewAccessibility")
class HandleMenuView(
private val context: Context,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
menuWidth: Int,
captionHeight: Int,
private val shouldShowWindowingPill: Boolean,
@@ -472,6 +492,7 @@ class HandleMenu(
private val shouldShowManageWindowsButton: Boolean,
private val shouldShowChangeAspectRatioButton: Boolean,
private val shouldShowDesktopModeButton: Boolean,
+ private val shouldShowRestartButton: Boolean,
private val isBrowserApp: Boolean
) {
val rootView = LayoutInflater.from(context)
@@ -549,6 +570,8 @@ class HandleMenu(
.requireViewById<HandleMenuActionButton>(R.id.manage_windows_button)
private val changeAspectRatioBtn = moreActionsPill
.requireViewById<HandleMenuActionButton>(R.id.change_aspect_ratio_button)
+ private val restartBtn = moreActionsPill
+ .requireViewById<HandleMenuActionButton>(R.id.handle_menu_restart_button)
// Open in Browser/App Pill.
private val openInAppOrBrowserPill = rootView.requireViewById<View>(
@@ -574,6 +597,7 @@ class HandleMenu(
var onChangeAspectRatioClickListener: (() -> Unit)? = null
var onOpenInAppOrBrowserClickListener: (() -> Unit)? = null
var onOpenByDefaultClickListener: (() -> Unit)? = null
+ var onRestartClickListener: (() -> Unit)? = null
var onCloseMenuClickListener: (() -> Unit)? = null
var onOutsideTouchListener: (() -> Unit)? = null
@@ -590,6 +614,7 @@ class HandleMenu(
newWindowBtn.setOnClickListener { onNewWindowClickListener?.invoke() }
manageWindowBtn.setOnClickListener { onManageWindowsClickListener?.invoke() }
changeAspectRatioBtn.setOnClickListener { onChangeAspectRatioClickListener?.invoke() }
+ restartBtn.setOnClickListener { onRestartClickListener?.invoke() }
rootView.setOnTouchListener { _, event ->
if (event.actionMasked == ACTION_OUTSIDE) {
@@ -599,6 +624,45 @@ class HandleMenu(
return@setOnTouchListener true
}
+ desktopBtn.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_APP_HANDLE_MENU_DESKTOP_VIEW)
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
+
+ fullscreenBtn.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_APP_HANDLE_MENU_FULLSCREEN)
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
+
+ splitscreenBtn.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_APP_HANDLE_MENU_SPLIT_SCREEN)
+ }
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
+
with(context) {
// Update a11y announcement out to say "double tap to enter Fullscreen"
ViewCompat.replaceAccessibilityAction(
@@ -762,6 +826,7 @@ class HandleMenu(
floatingBtn.isEnabled = !taskInfo.isPinned
floatingBtn.imageTintList = style.windowingButtonColor
desktopBtn.isGone = !shouldShowDesktopModeButton
+ desktopBtnSpace.isGone = !shouldShowDesktopModeButton
desktopBtn.isSelected = taskInfo.isFreeform
desktopBtn.isEnabled = !taskInfo.isFreeform
desktopBtn.imageTintList = style.windowingButtonColor
@@ -807,6 +872,7 @@ class HandleMenu(
newWindowBtn to shouldShowNewWindowButton,
manageWindowBtn to shouldShowManageWindowsButton,
changeAspectRatioBtn to shouldShowChangeAspectRatioButton,
+ restartBtn to shouldShowRestartButton,
).forEach { (button, shouldShow) ->
button.apply {
isGone = !shouldShow
@@ -871,6 +937,13 @@ class HandleMenu(
fun shouldShowChangeAspectRatioButton(taskInfo: RunningTaskInfo): Boolean =
taskInfo.appCompatTaskInfo.eligibleForUserAspectRatioButton() &&
taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+
+ /**
+ * Returns whether the restart button should be shown for the task. It usually means that
+ * the task has moved to a different display.
+ */
+ fun shouldShowRestartButton(taskInfo: RunningTaskInfo): Boolean =
+ taskInfo.appCompatTaskInfo.isRestartMenuEnabledForDisplayMove
}
}
@@ -889,8 +962,10 @@ interface HandleMenuFactory {
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
shouldShowDesktopModeButton: Boolean,
+ shouldShowRestartButton: Boolean,
isBrowserApp: Boolean,
openInAppOrBrowserIntent: Intent?,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -913,8 +988,10 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowManageWindowsButton: Boolean,
shouldShowChangeAspectRatioButton: Boolean,
shouldShowDesktopModeButton: Boolean,
+ shouldShowRestartButton: Boolean,
isBrowserApp: Boolean,
openInAppOrBrowserIntent: Intent?,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger,
captionWidth: Int,
captionHeight: Int,
captionX: Int,
@@ -933,8 +1010,10 @@ object DefaultHandleMenuFactory : HandleMenuFactory {
shouldShowManageWindowsButton,
shouldShowChangeAspectRatioButton,
shouldShowDesktopModeButton,
+ shouldShowRestartButton,
isBrowserApp,
openInAppOrBrowserIntent,
+ desktopModeUiEventLogger,
captionWidth,
captionHeight,
captionX,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 5d1a7a0cc3a2..a10826fca3e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -60,14 +60,16 @@ import android.window.TaskConstants
import androidx.compose.material3.ColorScheme
import androidx.compose.ui.graphics.toArgb
import androidx.core.animation.addListener
-import androidx.core.view.ViewCompat
-import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_MAXIMIZE_MENU_MAXIMIZE
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_MAXIMIZE_MENU_RESIZE_LEFT
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_MAXIMIZE_MENU_RESIZE_RIGHT
import com.android.wm.shell.desktopmode.isTaskMaximized
import com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE
import com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_LINEAR_IN
@@ -85,13 +87,14 @@ import java.util.function.Supplier
* to the right or left half of the screen.
*/
class MaximizeMenu(
- private val syncQueue: SyncTransactionQueue,
- private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
- private val displayController: DisplayController,
- private val taskInfo: RunningTaskInfo,
- private val decorWindowContext: Context,
- private val positionSupplier: (Int, Int) -> Point,
- private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
+ private val syncQueue: SyncTransactionQueue,
+ private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val displayController: DisplayController,
+ private val taskInfo: RunningTaskInfo,
+ private val decorWindowContext: Context,
+ private val positionSupplier: (Int, Int) -> Point,
+ private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger
) {
private var maximizeMenu: AdditionalViewHostViewContainer? = null
private var maximizeMenuView: MaximizeMenuView? = null
@@ -132,7 +135,7 @@ class MaximizeMenu(
onLeftSnapClickListener = onLeftSnapClickListener,
onRightSnapClickListener = onRightSnapClickListener,
onHoverListener = onHoverListener,
- onOutsideTouchListener = onOutsideTouchListener
+ onOutsideTouchListener = onOutsideTouchListener,
)
maximizeMenuView?.let { view ->
view.animateOpenMenu(onEnd = {
@@ -167,7 +170,7 @@ class MaximizeMenu(
onLeftSnapClickListener: () -> Unit,
onRightSnapClickListener: () -> Unit,
onHoverListener: (Boolean) -> Unit,
- onOutsideTouchListener: () -> Unit
+ onOutsideTouchListener: () -> Unit,
) {
val t = transactionSupplier.get()
val builder = SurfaceControl.Builder()
@@ -186,6 +189,7 @@ class MaximizeMenu(
"MaximizeMenu")
maximizeMenuView = MaximizeMenuView(
context = decorWindowContext,
+ desktopModeUiEventLogger = desktopModeUiEventLogger,
sizeToggleDirection = getSizeToggleDirection(),
immersiveConfig = if (showImmersiveOption) {
MaximizeMenuView.ImmersiveConfig.Visible(
@@ -265,6 +269,7 @@ class MaximizeMenu(
*/
class MaximizeMenuView(
context: Context,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
private val sizeToggleDirection: SizeToggleDirection,
immersiveConfig: ImmersiveConfig,
showSnapOptions: Boolean,
@@ -425,7 +430,10 @@ class MaximizeMenu(
) {
super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(AccessibilityAction.ACTION_CLICK)
+ info.addAction(AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.id,
+ context.getString(R.string.maximize_menu_talkback_action_maximize_restore_text)
+ ))
host.isClickable = true
}
@@ -435,6 +443,7 @@ class MaximizeMenu(
args: Bundle?
): Boolean {
if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_MAXIMIZE_MENU_MAXIMIZE)
onMaximizeClickListener?.invoke()
}
return super.performAccessibilityAction(host, action, args)
@@ -447,7 +456,10 @@ class MaximizeMenu(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(AccessibilityAction.ACTION_CLICK)
+ info.addAction(AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.id,
+ context.getString(R.string.maximize_menu_talkback_action_snap_left_text)
+ ))
host.isClickable = true
}
@@ -457,6 +469,7 @@ class MaximizeMenu(
args: Bundle?
): Boolean {
if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_MAXIMIZE_MENU_RESIZE_LEFT)
onLeftSnapClickListener?.invoke()
}
return super.performAccessibilityAction(host, action, args)
@@ -469,7 +482,10 @@ class MaximizeMenu(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(host, info)
- info.addAction(AccessibilityAction.ACTION_CLICK)
+ info.addAction(AccessibilityAction(
+ AccessibilityAction.ACTION_CLICK.id,
+ context.getString(R.string.maximize_menu_talkback_action_snap_right_text)
+ ))
host.isClickable = true
}
@@ -479,35 +495,13 @@ class MaximizeMenu(
args: Bundle?
): Boolean {
if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_MAXIMIZE_MENU_RESIZE_RIGHT)
onRightSnapClickListener?.invoke()
}
return super.performAccessibilityAction(host, action, args)
}
}
- with(context.resources) {
- ViewCompat.replaceAccessibilityAction(
- snapLeftButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.maximize_menu_talkback_action_snap_left_text),
- null
- )
-
- ViewCompat.replaceAccessibilityAction(
- snapRightButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.maximize_menu_talkback_action_snap_right_text),
- null
- )
-
- ViewCompat.replaceAccessibilityAction(
- sizeToggleButton,
- AccessibilityActionCompat.ACTION_CLICK,
- getString(R.string.maximize_menu_talkback_action_maximize_restore_text),
- null
- )
- }
-
// Maximize/restore button.
val sizeToggleBtnTextId = if (sizeToggleDirection == SizeToggleDirection.RESTORE)
R.string.desktop_mode_maximize_menu_restore_button_text
@@ -792,15 +786,15 @@ class MaximizeMenu(
}
/** Measure width of the root view of this menu. */
- fun measureWidth() : Int {
- rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- return rootView.getMeasuredWidth()
+ fun measureWidth(): Int {
+ rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ return rootView.measuredWidth
}
/** Measure height of the root view of this menu. */
- fun measureHeight() : Int {
- rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
- return rootView.getMeasuredHeight()
+ fun measureHeight(): Int {
+ rootView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+ return rootView.measuredHeight
}
private fun deactivateSnapOptions() {
@@ -1048,7 +1042,8 @@ interface MaximizeMenuFactory {
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
positionSupplier: (Int, Int) -> Point,
- transactionSupplier: Supplier<Transaction>
+ transactionSupplier: Supplier<Transaction>,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger,
): MaximizeMenu
}
@@ -1061,7 +1056,8 @@ object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
taskInfo: RunningTaskInfo,
decorWindowContext: Context,
positionSupplier: (Int, Int) -> Point,
- transactionSupplier: Supplier<Transaction>
+ transactionSupplier: Supplier<Transaction>,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger,
): MaximizeMenu {
return MaximizeMenu(
syncQueue,
@@ -1070,7 +1066,8 @@ object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
taskInfo,
decorWindowContext,
positionSupplier,
- transactionSupplier
+ transactionSupplier,
+ desktopModeUiEventLogger
)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
index c5057aa3cc18..f09f22fb1951 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/ThemeUtils.kt
@@ -92,4 +92,7 @@ internal class DecorThemeUtil(private val context: Context) {
Theme.LIGHT -> lightColors
Theme.DARK -> darkColors
}
+
+ fun getColorScheme(isDarkMode: Boolean): ColorScheme =
+ if (isDarkMode) darkColors else lightColors
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 57f8046065b4..ab6b72947586 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -198,6 +198,11 @@ class DesktopTilingDividerWindowManager(
tilingDividerView?.onUiModeChange(isDarkMode)
}
+ /** Notifies the divider view of task info change and possible color change. */
+ fun onTaskInfoChange() {
+ tilingDividerView?.onTaskInfoChange()
+ }
+
/** Hides the divider bar. */
fun hideDividerBar() {
if (!dividerShown) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index 854d9e1deef1..3e92d7d994a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -375,6 +375,7 @@ class DesktopTilingWindowDecoration(
fun onTaskInfoChange(taskInfo: RunningTaskInfo) {
val isCurrentTaskInDarkMode = isTaskInDarkMode(taskInfo)
+ desktopTilingDividerWindowManager?.onTaskInfoChange()
if (isCurrentTaskInDarkMode == isDarkMode || !isTilingManagerInitialised) return
isDarkMode = isCurrentTaskInDarkMode
desktopTilingDividerWindowManager?.onUiModeChange(isDarkMode)
@@ -432,15 +433,38 @@ class DesktopTilingWindowDecoration(
startTransaction: Transaction,
finishTransaction: Transaction,
) {
+ var leftTaskBroughtToFront = false
+ var rightTaskBroughtToFront = false
+
for (change in info.changes) {
change.taskInfo?.let {
if (it.isFullscreen || isMinimized(change.mode, info.type)) {
removeTaskIfTiled(it.taskId, /* taskVanished= */ false, it.isFullscreen)
} else if (isEnteringPip(change, info.type)) {
removeTaskIfTiled(it.taskId, /* taskVanished= */ true, it.isFullscreen)
+ } else if (isTransitionToFront(change.mode, info.type)) {
+ handleTaskBroughtToFront(it.taskId)
+ leftTaskBroughtToFront =
+ leftTaskBroughtToFront ||
+ it.taskId == leftTaskResizingHelper?.taskInfo?.taskId
+ rightTaskBroughtToFront =
+ rightTaskBroughtToFront ||
+ it.taskId == rightTaskResizingHelper?.taskInfo?.taskId
}
}
}
+
+ if (leftTaskBroughtToFront && rightTaskBroughtToFront) {
+ desktopTilingDividerWindowManager?.showDividerBar()
+ }
+ }
+
+ private fun handleTaskBroughtToFront(taskId: Int) {
+ if (taskId == leftTaskResizingHelper?.taskInfo?.taskId) {
+ leftTaskResizingHelper?.onAppBecomingVisible()
+ } else if (taskId == rightTaskResizingHelper?.taskInfo?.taskId) {
+ rightTaskResizingHelper?.onAppBecomingVisible()
+ }
}
private fun isMinimized(changeMode: Int, infoType: Int): Boolean {
@@ -471,6 +495,9 @@ class DesktopTilingWindowDecoration(
return false
}
+ private fun isTransitionToFront(changeMode: Int, transitionType: Int): Boolean =
+ changeMode == TRANSIT_TO_FRONT && transitionType == TRANSIT_TO_FRONT
+
class AppResizingHelper(
val taskInfo: RunningTaskInfo,
val desktopModeWindowDecoration: DesktopModeWindowDecoration,
@@ -484,6 +511,7 @@ class DesktopTilingWindowDecoration(
) {
var isInitialised = false
var newBounds = Rect(bounds)
+ var visibilityCallback: (() -> Unit)? = null
private lateinit var resizeVeil: ResizeVeil
private val displayContext = displayController.getDisplayContext(taskInfo.displayId)
private val userContext =
@@ -521,6 +549,11 @@ class DesktopTilingWindowDecoration(
fun updateVeil(t: Transaction) = resizeVeil.updateTransactionWithResizeVeil(t, newBounds)
+ fun onAppBecomingVisible() {
+ visibilityCallback?.invoke()
+ visibilityCallback = null
+ }
+
fun hideVeil() = resizeVeil.hideVeil()
private fun createIconFactory(context: Context, dimensions: Int): BaseIconFactory {
@@ -593,11 +626,16 @@ class DesktopTilingWindowDecoration(
removeTask(leftTaskResizingHelper, taskVanished, shouldDelayUpdate)
leftTaskResizingHelper = null
val taskId = rightTaskResizingHelper?.taskInfo?.taskId
- if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ val callback: (() -> Unit)? = {
rightTaskResizingHelper
?.desktopModeWindowDecoration
?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
}
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ callback?.invoke()
+ } else if (rightTaskResizingHelper != null) {
+ rightTaskResizingHelper?.visibilityCallback = callback
+ }
tearDownTiling()
return
}
@@ -607,11 +645,17 @@ class DesktopTilingWindowDecoration(
removeTask(rightTaskResizingHelper, taskVanished, shouldDelayUpdate)
rightTaskResizingHelper = null
val taskId = leftTaskResizingHelper?.taskInfo?.taskId
- if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ val callback: (() -> Unit)? = {
leftTaskResizingHelper
?.desktopModeWindowDecoration
?.updateDisabledResizingEdge(NONE, shouldDelayUpdate)
}
+ if (taskId != null && taskRepository.isVisibleTask(taskId)) {
+ callback?.invoke()
+ } else if (leftTaskResizingHelper != null) {
+ leftTaskResizingHelper?.visibilityCallback = callback
+ }
+
tearDownTiling()
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 54dcd2d082dc..e9bd6f8ef85c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -29,6 +29,7 @@ import android.view.RoundedCorner
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
+import androidx.compose.ui.graphics.toArgb
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags
import com.android.wm.shell.R
@@ -36,6 +37,7 @@ import com.android.wm.shell.common.split.DividerHandleView
import com.android.wm.shell.common.split.DividerRoundedCorner
import com.android.wm.shell.shared.animation.Interpolators
import com.android.wm.shell.windowdecor.DragDetector
+import com.android.wm.shell.windowdecor.common.DecorThemeUtil
/** Divider for tiling split screen, currently mostly a copy of [DividerView]. */
class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.MotionEventHandler {
@@ -56,6 +58,9 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
@VisibleForTesting var handleY: IntRange = 0..0
private var canResize = false
private var resized = false
+ private var isDarkMode = false
+ private var decorThemeUtil = DecorThemeUtil(context)
+
/**
* Tracks divider bar visible bounds in screen-based coordination. Used to calculate with
* insets.
@@ -90,10 +95,12 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
) {
callback = dividerMoveCallback
this.dividerBounds.set(dividerBounds)
+ this.isDarkMode = isDarkMode
+ paint.color = decorThemeUtil.getColorScheme(isDarkMode).outlineVariant.toArgb()
handle.setIsLeftRightSplit(true)
handle.setup(/* isSplitScreen= */ false, isDarkMode)
corners.setIsLeftRightSplit(true)
- corners.setup(/* isSplitScreen= */ false, isDarkMode)
+ corners.setup(/* isSplitScreen= */ false, paint.color)
handleRegionHeight = handleRegionSize.height
handleRegionWidth = handleRegionSize.width
cornersRadius =
@@ -108,17 +115,22 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
}
fun onUiModeChange(isDarkMode: Boolean) {
+ this.isDarkMode = isDarkMode
handle.onUiModeChange(isDarkMode)
- corners.onUiModeChange(isDarkMode)
- paint.color =
- if (isDarkMode) {
- resources.getColor(R.color.tiling_divider_background_dark, null /* theme */)
- } else {
- resources.getColor(R.color.tiling_divider_background_light, null /* theme */)
- }
+ paint.color = decorThemeUtil.getColorScheme(isDarkMode).outlineVariant.toArgb()
+ corners.onUiModeChange(paint.color)
invalidate()
}
+ fun onTaskInfoChange() {
+ decorThemeUtil = DecorThemeUtil(context)
+ if (paint.color != decorThemeUtil.getColorScheme(isDarkMode).outlineVariant.toArgb()) {
+ paint.color = decorThemeUtil.getColorScheme(isDarkMode).outlineVariant.toArgb()
+ corners.onCornerColorChange(paint.color)
+ invalidate()
+ }
+ }
+
override fun onFinishInflate() {
super.onFinishInflate()
dividerBar = requireViewById(R.id.divider_bar)
@@ -128,15 +140,10 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
resources.getDimensionPixelSize(R.dimen.docked_stack_divider_lift_elevation)
setOnTouchListener(this)
setWillNotDraw(false)
- paint.color =
- if (
- context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
- Configuration.UI_MODE_NIGHT_YES
- ) {
- resources.getColor(R.color.tiling_divider_background_dark, /* theme= */null)
- } else {
- resources.getColor(R.color.tiling_divider_background_light, /* theme= */ null)
- }
+ val isDarkMode =
+ context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES
+ paint.color = decorThemeUtil.getColorScheme(isDarkMode).outlineVariant.toArgb()
paint.isAntiAlias = true
paint.style = Paint.Style.FILL
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 9d16be59ba34..f09c500dcdd0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -39,6 +39,8 @@ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.Accessibilit
import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags
import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_HANDLE_MENU_OPENED
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.windowdecor.AppHandleAnimator
import com.android.wm.shell.windowdecor.WindowManagerWrapper
@@ -53,7 +55,8 @@ class AppHandleViewHolder(
onCaptionTouchListener: View.OnTouchListener,
onCaptionButtonClickListener: OnClickListener,
private val windowManagerWrapper: WindowManagerWrapper,
- private val handler: Handler
+ private val handler: Handler,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
) : WindowDecorationViewHolder<AppHandleViewHolder.HandleData>(rootView) {
data class HandleData(
@@ -201,6 +204,7 @@ class AppHandleViewHolder(
// Passthrough the a11y click action so the caption handle, so that app handle menu
// is opened on a11y click, similar to a real click
if (action == AccessibilityAction.ACTION_CLICK.id) {
+ desktopModeUiEventLogger.log(taskInfo, A11Y_APP_HANDLE_MENU_OPENED)
captionHandle.performClick()
}
return super.performAccessibilityAction(host, action, args)
@@ -216,10 +220,13 @@ class AppHandleViewHolder(
}
}
- // Update a11y action text so that Talkback announces "Press double tap to open app handle
- // menu" while focused on status bar input layer
+ // Update a11y action text so that Talkback announces "Press double tap to open menu"
+ // while focused on status bar input layer
ViewCompat.replaceAccessibilityAction(
- view, AccessibilityActionCompat.ACTION_CLICK, "Open app handle menu", null
+ view,
+ AccessibilityActionCompat.ACTION_CLICK,
+ context.getString(R.string.app_handle_chip_accessibility_announce),
+ null
)
}
@@ -297,12 +304,14 @@ class AppHandleViewHolder(
onCaptionButtonClickListener: OnClickListener,
windowManagerWrapper: WindowManagerWrapper,
handler: Handler,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger,
): AppHandleViewHolder = AppHandleViewHolder(
rootView,
onCaptionTouchListener,
onCaptionButtonClickListener,
windowManagerWrapper,
handler,
+ desktopModeUiEventLogger,
)
}
}
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 0e2698d0b6fa..d2e3a07f4651 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
@@ -49,6 +49,13 @@ import com.android.internal.R.color.materialColorSurfaceContainerHigh
import com.android.internal.R.color.materialColorSurfaceContainerLow
import com.android.internal.R.color.materialColorSurfaceDim
import com.android.wm.shell.R
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_ACTION_MAXIMIZE_RESTORE
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_ACTION_RESIZE_LEFT
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_ACTION_RESIZE_RIGHT
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_WINDOW_CLOSE_BUTTON
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_WINDOW_MAXIMIZE_RESTORE_BUTTON
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger.DesktopUiEventEnum.A11Y_APP_WINDOW_MINIMIZE_BUTTON
import com.android.wm.shell.windowdecor.MaximizeButtonView
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.DrawableInsets
@@ -75,6 +82,7 @@ class AppHeaderViewHolder(
mOnRightSnapClickListener: () -> Unit,
mOnMaximizeOrRestoreClickListener: () -> Unit,
onMaximizeHoverAnimationFinishedListener: () -> Unit,
+ private val desktopModeUiEventLogger: DesktopModeUiEventLogger,
) : WindowDecorationViewHolder<AppHeaderViewHolder.HeaderData>(rootView) {
data class HeaderData(
@@ -151,6 +159,8 @@ class AppHeaderViewHolder(
private lateinit var a11yTextMaximize: String
private lateinit var a11yTextRestore: String
+ private lateinit var currentTaskInfo: RunningTaskInfo
+
init {
captionView.setOnTouchListener(onCaptionTouchListener)
captionHandle.setOnTouchListener(onCaptionTouchListener)
@@ -197,9 +207,18 @@ class AppHeaderViewHolder(
args: Bundle?
): Boolean {
when (action) {
- R.id.action_snap_left -> mOnLeftSnapClickListener.invoke()
- R.id.action_snap_right -> mOnRightSnapClickListener.invoke()
- R.id.action_maximize_restore -> mOnMaximizeOrRestoreClickListener.invoke()
+ R.id.action_snap_left -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_RESIZE_LEFT)
+ mOnLeftSnapClickListener.invoke()
+ }
+ R.id.action_snap_right -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_RESIZE_RIGHT)
+ mOnRightSnapClickListener.invoke()
+ }
+ R.id.action_maximize_restore -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_MAXIMIZE_RESTORE)
+ mOnMaximizeOrRestoreClickListener.invoke()
+ }
}
return super.performAccessibilityAction(host, action, args)
@@ -224,10 +243,56 @@ class AppHeaderViewHolder(
args: Bundle?
): Boolean {
when (action) {
- AccessibilityAction.ACTION_CLICK.id -> host.performClick()
- R.id.action_snap_left -> mOnLeftSnapClickListener.invoke()
- R.id.action_snap_right -> mOnRightSnapClickListener.invoke()
- R.id.action_maximize_restore -> mOnMaximizeOrRestoreClickListener.invoke()
+ AccessibilityAction.ACTION_CLICK.id -> {
+ desktopModeUiEventLogger.log(
+ currentTaskInfo, A11Y_APP_WINDOW_MAXIMIZE_RESTORE_BUTTON
+ )
+ host.performClick()
+ }
+ R.id.action_snap_left -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_RESIZE_LEFT)
+ mOnLeftSnapClickListener.invoke()
+ }
+ R.id.action_snap_right -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_RESIZE_RIGHT)
+ mOnRightSnapClickListener.invoke()
+ }
+ R.id.action_maximize_restore -> {
+ desktopModeUiEventLogger.log(currentTaskInfo, A11Y_ACTION_MAXIMIZE_RESTORE)
+ mOnMaximizeOrRestoreClickListener.invoke()
+ }
+ }
+
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
+
+ closeWindowButton.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ when (action) {
+ AccessibilityAction.ACTION_CLICK.id -> desktopModeUiEventLogger.log(
+ currentTaskInfo, A11Y_APP_WINDOW_CLOSE_BUTTON
+ )
+ }
+
+ return super.performAccessibilityAction(host, action, args)
+ }
+ }
+
+ minimizeWindowButton.accessibilityDelegate = object : View.AccessibilityDelegate() {
+ override fun performAccessibilityAction(
+ host: View,
+ action: Int,
+ args: Bundle?
+ ): Boolean {
+ when (action) {
+ AccessibilityAction.ACTION_CLICK.id -> desktopModeUiEventLogger.log(
+ currentTaskInfo, A11Y_APP_WINDOW_MINIMIZE_BUTTON
+ )
}
return super.performAccessibilityAction(host, action, args)
@@ -310,7 +375,8 @@ class AppHeaderViewHolder(
enableMaximizeLongClick: Boolean,
isCaptionVisible: Boolean,
) {
- if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) {
+ currentTaskInfo = taskInfo
+ if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue) {
bindDataWithThemedHeaders(
taskInfo,
isTaskMaximized,
@@ -361,7 +427,7 @@ class AppHeaderViewHolder(
minimizeWindowButton.background = getDrawable(1)
}
maximizeButtonView.setAnimationTints(isDarkMode())
- minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()
+ minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue
}
private fun bindDataWithThemedHeaders(
@@ -417,7 +483,7 @@ class AppHeaderViewHolder(
drawableInsets = minimizeDrawableInsets
)
}
- minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue()
+ minimizeWindowButton.isGone = !DesktopModeFlags.ENABLE_MINIMIZE_BUTTON.isTrue
// Maximize button.
maximizeButtonView.apply {
setAnimationTints(
@@ -761,6 +827,7 @@ class AppHeaderViewHolder(
mOnRightSnapClickListener: () -> Unit,
mOnMaximizeOrRestoreClickListener: () -> Unit,
onMaximizeHoverAnimationFinishedListener: () -> Unit,
+ desktopModeUiEventLogger: DesktopModeUiEventLogger
): AppHeaderViewHolder = AppHeaderViewHolder(
rootView,
onCaptionTouchListener,
@@ -771,6 +838,7 @@ class AppHeaderViewHolder(
mOnRightSnapClickListener,
mOnMaximizeOrRestoreClickListener,
onMaximizeHoverAnimationFinishedListener,
+ desktopModeUiEventLogger,
)
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
index 3d5e9495e29d..1e459d55ed77 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayControllerTests.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.common;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -231,4 +232,24 @@ public class DisplayControllerTests extends ShellTestCase {
assertEquals(DISPLAY_ABS_BOUNDS_1,
mController.getDisplayLayout(DISPLAY_ID_1).globalBoundsDp());
}
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG)
+ public void onDisplayConfigurationChanged_reInitDisplayLayout()
+ throws RemoteException {
+ ExtendedMockito.doReturn(true)
+ .when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
+ mController.onInit();
+ mController.addDisplayWindowListener(mListener);
+
+ mCapturedTopologyListener.accept(mMockTopology);
+
+ DisplayLayout displayLayoutBefore = mController.getDisplayLayout(DISPLAY_ID_0);
+ mDisplayContainerListener.onDisplayConfigurationChanged(DISPLAY_ID_0, new Configuration());
+ DisplayLayout displayLayoutAfter = mController.getDisplayLayout(DISPLAY_ID_0);
+
+ assertNotSame(displayLayoutBefore, displayLayoutAfter);
+ assertEquals(DISPLAY_ABS_BOUNDS_0,
+ mController.getDisplayLayout(DISPLAY_ID_0).globalBoundsDp());
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
deleted file mode 100644
index 25dbc64f83de..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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.wm.shell.common.pip;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
-import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
-import static com.android.wm.shell.Flags.FLAG_ENABLE_PIP2;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.window.DisplayAreaInfo;
-import android.window.WindowContainerToken;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-/**
- * Unit test against {@link PipDesktopState}.
- */
-@SmallTest
-@TestableLooper.RunWithLooper
-@RunWith(AndroidTestingRunner.class)
-@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
-public class PipDesktopStateTest {
- @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
- @Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
- @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopRepository mMockDesktopRepository;
- @Mock
- private Optional<DragToDesktopTransitionHandler> mMockDragToDesktopTransitionHandlerOptional;
- @Mock private DragToDesktopTransitionHandler mMockDragToDesktopTransitionHandler;
-
- @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
- @Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
-
- private static final int DISPLAY_ID = 1;
- private DisplayAreaInfo mDefaultTda;
- private PipDesktopState mPipDesktopState;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
- when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
- when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
-
- when(mMockDragToDesktopTransitionHandlerOptional.get()).thenReturn(
- mMockDragToDesktopTransitionHandler);
- when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(true);
-
- when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
- when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
-
- mDefaultTda = new DisplayAreaInfo(Mockito.mock(WindowContainerToken.class), DISPLAY_ID, 0);
- when(mMockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
- mDefaultTda);
-
- mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
- mMockDesktopUserRepositoriesOptional,
- mMockDragToDesktopTransitionHandlerOptional,
- mMockRootTaskDisplayAreaOrganizer);
- }
-
- @Test
- public void isDesktopWindowingPipEnabled_returnsTrue() {
- assertTrue(mPipDesktopState.isDesktopWindowingPipEnabled());
- }
-
- @Test
- public void isDesktopWindowingPipEnabled_desktopRepositoryEmpty_returnsFalse() {
- when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(false);
-
- assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
- }
-
- @Test
- public void isDesktopWindowingPipEnabled_dragToDesktopTransitionHandlerEmpty_returnsFalse() {
- when(mMockDragToDesktopTransitionHandlerOptional.isPresent()).thenReturn(false);
-
- assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
- }
-
- @Test
- @EnableFlags({
- FLAG_ENABLE_CONNECTED_DISPLAYS_PIP, FLAG_ENABLE_PIP2
- })
- public void isConnectedDisplaysPipEnabled_returnsTrue() {
- assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
- }
-
- @Test
- public void isPipInDesktopMode_anyDeskActive_returnsTrue() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
-
- assertTrue(mPipDesktopState.isPipInDesktopMode());
- }
-
- @Test
- public void isPipInDesktopMode_noDeskActive_returnsFalse() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false);
-
- assertFalse(mPipDesktopState.isPipInDesktopMode());
- }
-
- @Test
- public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
-
- assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
- }
-
- @Test
- public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
- when(mMockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true);
- setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- assertEquals(WINDOWING_MODE_FREEFORM, mPipDesktopState.getOutPipWindowingMode());
- }
-
- @Test
- public void getOutPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
- setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
-
- assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
- }
-
- @Test
- public void isDragToDesktopInProgress_inProgress_returnsTrue() {
- when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(true);
-
- assertTrue(mPipDesktopState.isDragToDesktopInProgress());
- }
-
- @Test
- public void isDragToDesktopInProgress_notInProgress_returnsFalse() {
- when(mMockDragToDesktopTransitionHandler.getInProgress()).thenReturn(false);
-
- assertFalse(mPipDesktopState.isDragToDesktopInProgress());
- }
-
- private void setDisplayWindowingMode(int windowingMode) {
- mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.kt
new file mode 100644
index 000000000000..2c50cd9d0c81
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.wm.shell.common.pip
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.platform.test.annotations.EnableFlags
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
+import com.android.wm.shell.Flags.FLAG_ENABLE_PIP2
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+/**
+ * Unit test against [PipDesktopState].
+ */
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+class PipDesktopStateTest : ShellTestCase() {
+ private val mockPipDisplayLayoutState = mock<PipDisplayLayoutState>()
+ private val mockDesktopUserRepositories = mock<DesktopUserRepositories>()
+ private val mockDesktopRepository = mock<DesktopRepository>()
+ private val mockDragToDesktopTransitionHandler = mock<DragToDesktopTransitionHandler>()
+ private val mockRootTaskDisplayAreaOrganizer = mock<RootTaskDisplayAreaOrganizer>()
+ private val mockTaskInfo = mock<ActivityManager.RunningTaskInfo>()
+ private lateinit var defaultTda: DisplayAreaInfo
+ private lateinit var pipDesktopState: PipDesktopState
+
+ @Before
+ fun setUp() {
+ whenever(mockDesktopUserRepositories.current).thenReturn(mockDesktopRepository)
+ whenever(mockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID)
+ whenever(mockPipDisplayLayoutState.displayId).thenReturn(DISPLAY_ID)
+
+ defaultTda = DisplayAreaInfo(mock<WindowContainerToken>(), DISPLAY_ID, /* featureId = */ 0)
+ whenever(mockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
+ defaultTda
+ )
+
+ pipDesktopState =
+ PipDesktopState(
+ mockPipDisplayLayoutState,
+ Optional.of(mockDesktopUserRepositories),
+ Optional.of(mockDragToDesktopTransitionHandler),
+ mockRootTaskDisplayAreaOrganizer
+ )
+ }
+
+ @Test
+ fun isDesktopWindowingPipEnabled_returnsTrue() {
+ assertThat(pipDesktopState.isDesktopWindowingPipEnabled()).isTrue()
+ }
+
+ @Test
+ @EnableFlags(
+ FLAG_ENABLE_CONNECTED_DISPLAYS_PIP,
+ FLAG_ENABLE_PIP2
+ )
+ fun isConnectedDisplaysPipEnabled_returnsTrue() {
+ assertThat(pipDesktopState.isConnectedDisplaysPipEnabled()).isTrue()
+ }
+
+ @Test
+ fun isPipInDesktopMode_anyDeskActive_returnsTrue() {
+ whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)
+
+ assertThat(pipDesktopState.isPipInDesktopMode()).isTrue()
+ }
+
+ @Test
+ fun isPipInDesktopMode_noDeskActive_returnsFalse() {
+ whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(false)
+
+ assertThat(pipDesktopState.isPipInDesktopMode()).isFalse()
+ }
+
+ @Test
+ fun outPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
+ whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)
+ setDisplayWindowingMode(WINDOWING_MODE_FREEFORM)
+
+ assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun outPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
+ whenever(mockDesktopRepository.isAnyDeskActive(DISPLAY_ID)).thenReturn(true)
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN)
+
+ assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
+ fun outPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN)
+
+ assertThat(pipDesktopState.getOutPipWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED)
+ }
+
+ @Test
+ fun isDragToDesktopInProgress_inProgress_returnsTrue() {
+ whenever(mockDragToDesktopTransitionHandler.inProgress).thenReturn(true)
+
+ assertThat(pipDesktopState.isDragToDesktopInProgress()).isTrue()
+ }
+
+ @Test
+ fun isDragToDesktopInProgress_notInProgress_returnsFalse() {
+ whenever(mockDragToDesktopTransitionHandler.inProgress).thenReturn(false)
+
+ assertThat(pipDesktopState.isDragToDesktopInProgress()).isFalse()
+ }
+
+ private fun setDisplayWindowingMode(windowingMode: Int) {
+ defaultTda.configuration.windowConfiguration.windowingMode = windowingMode
+ }
+
+ companion object {
+ private const val DISPLAY_ID = 1
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
index 22a85fc49a4b..9f2534eb2662 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/FlexParallaxSpecTests.java
@@ -71,6 +71,7 @@ public class FlexParallaxSpecTests {
when(mockSnapAlgorithm.getMiddleTarget()).thenReturn(mockMiddleTarget);
when(mockSnapAlgorithm.getLastSplitTarget()).thenReturn(mockLastTarget);
when(mockSnapAlgorithm.getDismissEndTarget()).thenReturn(mockEndEdge);
+ when(mockSnapAlgorithm.areOffscreenRatiosSupported()).thenReturn(true);
when(mockStartEdge.getPosition()).thenReturn(0);
when(mockFirstTarget.getPosition()).thenReturn(250);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 597e4a55ed0e..9035df28aa7c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -680,7 +680,8 @@ public class CompatUIControllerTest extends ShellTestCase {
// Create transparent task
final TaskInfo taskInfo1 = createTaskInfo(DISPLAY_ID, newTaskId, /* hasSizeCompat= */ true,
- /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ true);
+ /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ true,
+ /* isRestartMenuEnabledForDisplayMove */ true);
// Simulate new task being shown
mController.updateActiveTaskInfo(taskInfo1);
@@ -742,32 +743,38 @@ public class CompatUIControllerTest extends ShellTestCase {
@Test
@RequiresFlagsDisabled(Flags.FLAG_APP_COMPAT_UI_FRAMEWORK)
public void testSendCompatUIRequest_createRestartDialog() {
- TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ false);
- doReturn(true).when(mMockRestartDialogLayout)
- .needsToBeRecreated(any(TaskInfo.class),
- any(ShellTaskOrganizer.TaskListener.class));
+ final TaskInfo taskInfo = createTaskInfo(DISPLAY_ID, TASK_ID, /* hasSizeCompat= */ true,
+ /* isVisible */ true, /* isFocused */ true, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ true);
doReturn(true).when(mCompatUIConfiguration).isRestartDialogEnabled();
doReturn(true).when(mCompatUIConfiguration).shouldShowRestartDialogAgain(eq(taskInfo));
- mController.sendCompatUIRequest(new CompatUIRequests.DisplayCompatShowRestartDialog(
- taskInfo, mMockTaskListener));
+ mController.onCompatInfoChanged(new CompatUIInfo(taskInfo, mMockTaskListener));
verify(mController).createRestartDialogWindowManager(any(), eq(taskInfo),
eq(mMockTaskListener));
+ verify(mMockRestartDialogLayout).setRequestRestartDialog(false);
+
+ mController.sendCompatUIRequest(
+ new CompatUIRequests.DisplayCompatShowRestartDialog(taskInfo.taskId));
+ verify(mMockRestartDialogLayout).setRequestRestartDialog(true);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat) {
return createTaskInfo(displayId, taskId, hasSizeCompat, /* isVisible */ false,
- /* isFocused */ false, /* isTopActivityTransparent */ false);
+ /* isFocused */ false, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
boolean isVisible, boolean isFocused) {
return createTaskInfo(displayId, taskId, hasSizeCompat,
- isVisible, isFocused, /* isTopActivityTransparent */ false);
+ isVisible, isFocused, /* isTopActivityTransparent */ false,
+ /* isRestartMenuEnabledForDisplayMove */ false);
}
private static TaskInfo createTaskInfo(int displayId, int taskId, boolean hasSizeCompat,
- boolean isVisible, boolean isFocused, boolean isTopActivityTransparent) {
+ boolean isVisible, boolean isFocused, boolean isTopActivityTransparent,
+ boolean isRestartMenuEnabledForDisplayMove) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
taskInfo.displayId = displayId;
@@ -777,6 +784,8 @@ public class CompatUIControllerTest extends ShellTestCase {
taskInfo.isTopActivityTransparent = isTopActivityTransparent;
taskInfo.appCompatTaskInfo.setLetterboxEducationEnabled(true);
taskInfo.appCompatTaskInfo.setTopActivityLetterboxed(true);
+ taskInfo.appCompatTaskInfo.setRestartMenuEnabledForDisplayMove(
+ isRestartMenuEnabledForDisplayMove);
return taskInfo;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index d58f8a34c98e..94fe03084989 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -37,6 +37,8 @@ import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.window.flags.Flags.FLAG_RESPECT_ORIENTATION_CHANGE_FOR_UNRESIZEABLE
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.desktopmode.DesktopTestHelpers.createFreeformTask
@@ -96,12 +98,15 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
@Mock lateinit var repositoryInitializer: DesktopRepositoryInitializer
@Mock lateinit var userManager: UserManager
@Mock lateinit var shellController: ShellController
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var displayLayout: DisplayLayout
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var handler: DesktopActivityOrientationChangeHandler
private lateinit var shellInit: ShellInit
private lateinit var userRepositories: DesktopUserRepositories
private lateinit var testScope: CoroutineScope
+
// Mock running tasks are registered here so we can get the list from mock shell task organizer.
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -131,6 +136,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
whenever(runBlocking { persistentRepository.readDesktop(any(), any()) })
.thenReturn(Desktop.getDefaultInstance())
+ whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
handler =
DesktopActivityOrientationChangeHandler(
@@ -140,6 +146,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
taskStackListener,
resizeTransitionHandler,
userRepositories,
+ displayController,
)
shellInit.init()
@@ -171,6 +178,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
taskStackListener,
resizeTransitionHandler,
userRepositories,
+ displayController,
)
verify(shellInit, never())
@@ -251,6 +259,11 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
val oldBounds = task.configuration.windowConfiguration.bounds
val newTask =
setUpFreeformTask(isResizeable = false, orientation = SCREEN_ORIENTATION_LANDSCAPE)
+ whenever(displayLayout.height()).thenReturn(800)
+ whenever(displayLayout.width()).thenReturn(2000)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(Rect(0, 0, 2000, 800))
+ }
handler.handleActivityOrientationChange(task, newTask)
@@ -279,6 +292,11 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
bounds = oldBounds,
)
val newTask = setUpFreeformTask(isResizeable = false, bounds = oldBounds)
+ whenever(displayLayout.height()).thenReturn(2000)
+ whenever(displayLayout.width()).thenReturn(800)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(Rect(0, 0, 800, 2000))
+ }
handler.handleActivityOrientationChange(task, newTask)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
index 2aebcdcc3bf5..9268db60aa51 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -24,13 +24,16 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.window.flags.Flags
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.sysui.UserChangeListener
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
@@ -46,6 +49,7 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.whenever
import org.mockito.quality.Strictness
@@ -60,6 +64,8 @@ import org.mockito.quality.Strictness
class DesktopDisplayEventHandlerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
@Mock lateinit var displayController: DisplayController
+ @Mock private lateinit var mockShellController: ShellController
+ @Mock private lateinit var mockRootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock private lateinit var mockDesktopUserRepositories: DesktopUserRepositories
@Mock private lateinit var mockDesktopRepository: DesktopRepository
@Mock private lateinit var mockDesktopTasksController: DesktopTasksController
@@ -89,7 +95,9 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
context,
shellInit,
testScope.backgroundScope,
+ mockShellController,
displayController,
+ mockRootTaskDisplayAreaOrganizer,
desktopRepositoryInitializer,
mockDesktopUserRepositories,
mockDesktopTasksController,
@@ -107,6 +115,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_createsDesk() =
testScope.runTest {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -119,6 +128,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testDisplayAdded_supportsDesks_desktopRepositoryNotInitialized_doesNotCreateDesk() =
testScope.runTest {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -130,6 +140,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testDisplayAdded_supportsDesks_desktopRepositoryInitializedTwice_createsDeskOnce() =
testScope.runTest {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -143,6 +154,7 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun testDisplayAdded_supportsDesks_desktopRepositoryInitialized_deskExists_doesNotCreateDesk() =
testScope.runTest {
whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
@@ -156,33 +168,71 @@ class DesktopDisplayEventHandlerTest : ShellTestCase() {
}
@Test
- fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() {
- whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testDisplayAdded_cannotEnterDesktopMode_doesNotCreateDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(false)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
- onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ onDisplaysChangedListenerCaptor.lastValue.onDisplayAdded(DEFAULT_DISPLAY)
+ runCurrent()
- verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
- }
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
@Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun testDeskRemoved_noDesksRemain_createsDesk() {
- whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0)
+ fun testDeskRemoved_noDesksRemain_createsDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(0)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
- handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+ runCurrent()
- verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
- }
+ verify(mockDesktopTasksController).createDesk(DEFAULT_DISPLAY)
+ }
@Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
- fun testDeskRemoved_desksRemain_doesNotCreateDesk() {
- whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+ fun testDeskRemoved_desksRemain_doesNotCreateDesk() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ whenever(mockDesktopRepository.getNumberOfDesks(DEFAULT_DISPLAY)).thenReturn(1)
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+
+ handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+ runCurrent()
- handler.onDeskRemoved(DEFAULT_DISPLAY, deskId = 1)
+ verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
+ }
- verify(mockDesktopTasksController, never()).createDesk(DEFAULT_DISPLAY)
- }
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun testUserChanged_createsDeskWhenNeeded() =
+ testScope.runTest {
+ whenever(DesktopModeStatus.canEnterDesktopMode(context)).thenReturn(true)
+ val userChangeListenerCaptor = argumentCaptor<UserChangeListener>()
+ verify(mockShellController).addUserChangeListener(userChangeListenerCaptor.capture())
+ whenever(mockDesktopRepository.getNumberOfDesks(displayId = 2)).thenReturn(0)
+ whenever(mockDesktopRepository.getNumberOfDesks(displayId = 3)).thenReturn(0)
+ whenever(mockDesktopRepository.getNumberOfDesks(displayId = 4)).thenReturn(1)
+ whenever(mockRootTaskDisplayAreaOrganizer.displayIds).thenReturn(intArrayOf(2, 3, 4))
+ desktopRepositoryInitializer.initialize(mockDesktopUserRepositories)
+ handler.onDisplayAdded(displayId = 2)
+ handler.onDisplayAdded(displayId = 3)
+ handler.onDisplayAdded(displayId = 4)
+ runCurrent()
+
+ clearInvocations(mockDesktopTasksController)
+ userChangeListenerCaptor.lastValue.onUserChanged(1, context)
+ runCurrent()
+
+ verify(mockDesktopTasksController).createDesk(displayId = 2)
+ verify(mockDesktopTasksController).createDesk(displayId = 3)
+ verify(mockDesktopTasksController, never()).createDesk(displayId = 4)
+ }
@Test
fun testConnectExternalDisplay() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index d510570e8839..e40da5e8498d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -50,7 +50,6 @@ import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.FocusTransitionObserver
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel
-import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -120,11 +119,11 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
doAnswer {
- keyGestureEventHandler = (it.arguments[0] as KeyGestureEventHandler)
+ keyGestureEventHandler = (it.arguments[1] as KeyGestureEventHandler)
null
}
.whenever(inputManager)
- .registerKeyGestureEventHandler(any())
+ .registerKeyGestureEventHandler(any(), any())
shellInit.init()
desktopModeKeyGestureHandler =
@@ -176,10 +175,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setKeycodes(intArrayOf(KeyEvent.KEYCODE_D))
.setModifierState(KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON)
.build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
- assertThat(result).isTrue()
verify(desktopTasksController).moveToNextDisplay(task.taskId)
}
@@ -197,10 +195,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setKeycodes(intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET))
.setModifierState(KeyEvent.META_META_ON)
.build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
- assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel)
.onSnapResize(
task.taskId,
@@ -224,10 +221,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setKeycodes(intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET))
.setModifierState(KeyEvent.META_META_ON)
.build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
- assertThat(result).isTrue()
verify(desktopModeWindowDecorViewModel)
.onSnapResize(
task.taskId,
@@ -251,10 +247,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setKeycodes(intArrayOf(KeyEvent.KEYCODE_EQUALS))
.setModifierState(KeyEvent.META_META_ON)
.build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
- assertThat(result).isTrue()
verify(desktopTasksController)
.toggleDesktopTaskSize(
task,
@@ -280,10 +275,9 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.setKeycodes(intArrayOf(KeyEvent.KEYCODE_MINUS))
.setModifierState(KeyEvent.META_META_ON)
.build()
- val result = keyGestureEventHandler.handleKeyGestureEvent(event, null)
+ keyGestureEventHandler.handleKeyGestureEvent(event, null)
testExecutor.flushAll()
- assertThat(result).isTrue()
verify(desktopTasksController).minimizeTask(task, MinimizeReason.KEY_GESTURE)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 3fb008377d6e..6ede990df15e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -1280,6 +1280,18 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
@Test
@EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_active_marksInactiveInDisplay() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 3)
+
+ assertThat(repo.getActiveDeskId(displayId = DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun removeDesk_multipleDesks_inactive_removes() {
repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
@@ -1292,6 +1304,18 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_inactive_keepsOtherDeskActiveInDisplay() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 2)
+
+ assertThat(repo.getActiveDeskId(displayId = DEFAULT_DISPLAY)).isEqualTo(3)
+ }
+
+ @Test
@EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND, FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
fun removeDesk_removesFromPersistence() =
runTest(StandardTestDispatcher()) {
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 71e46bcb0698..b577667d8279 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
@@ -1526,6 +1526,37 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ fun launchIntent_taskInDesktopMode_onSecondaryDisplay_transitionStarted() {
+ setUpLandscapeDisplay()
+ taskRepository.addDesk(SECOND_DISPLAY, deskId = 2)
+ val intent = Intent().setComponent(homeComponentName)
+ whenever(
+ desktopMixedTransitionHandler.startLaunchTransition(
+ eq(TRANSIT_OPEN),
+ any(),
+ anyOrNull(),
+ anyOrNull(),
+ anyOrNull(),
+ )
+ )
+ .thenReturn(Binder())
+
+ controller.startLaunchIntentTransition(intent, Bundle.EMPTY, SECOND_DISPLAY)
+
+ val wct = getLatestDesktopMixedTaskWct(type = TRANSIT_OPEN)
+ // We expect two actions: open the app and start the desk
+ assertThat(wct.hierarchyOps).hasSize(2)
+ val hOps0 = wct.hierarchyOps[0]
+ val hOps1 = wct.hierarchyOps[1]
+ assertThat(hOps0.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ val activityOptions0 = ActivityOptions.fromBundle(hOps0.launchOptions)
+ assertThat(activityOptions0.launchDisplayId).isEqualTo(SECOND_DISPLAY)
+ assertThat(hOps1.type).isEqualTo(HIERARCHY_OP_TYPE_PENDING_INTENT)
+ val activityOptions1 = ActivityOptions.fromBundle(hOps1.launchOptions)
+ assertThat(activityOptions1.launchDisplayId).isEqualTo(SECOND_DISPLAY)
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun addMoveToDeskTaskChanges_landscapeDevice_userFullscreenOverride_defaultPortraitBounds() {
setUpLandscapeDisplay()
@@ -7615,6 +7646,106 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
assertNull(latestWct.hierarchyOps.find { op -> op.container == wallpaperToken.asBinder() })
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_returningToApp_noDeskDeactivation() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = true,
+ )
+
+ verify(desksOrganizer, never()).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(
+ argThat { t -> t.token == transition && t is DeskTransition.DeactivateDesk }
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_noActiveDesk_noDeskDeactivation() {
+ val deskId = 0
+ taskRepository.setDeskInactive(deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ )
+
+ verify(desksOrganizer, never()).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver, never())
+ .addPendingTransition(
+ argThat { t -> t.token == transition && t is DeskTransition.DeactivateDesk }
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_activeDesk_notReturningToDesk_deactivatesDesk() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ )
+
+ verify(desksOrganizer).deactivateDesk(finishWct, deskId)
+ verify(desksTransitionsObserver)
+ .addPendingTransition(DeskTransition.DeactivateDesk(transition, deskId))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_activeDesk_notReturningToDesk_notifiesDesktopExit() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ )
+
+ verify(desktopModeEnterExitTransitionListener).onExitDesktopModeTransitionStarted(any())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onRecentsInDesktopAnimationFinishing_activeDesk_notReturningToDesk_doesNotBringUpWallpaperOrHome() {
+ val deskId = 0
+ taskRepository.setActiveDesk(DEFAULT_DISPLAY, deskId)
+
+ val transition = Binder()
+ val finishWct = WindowContainerTransaction()
+ controller.onRecentsInDesktopAnimationFinishing(
+ transition = transition,
+ finishWct = finishWct,
+ returnToApp = false,
+ )
+
+ finishWct.assertWithoutHop { hop ->
+ hop.type == HIERARCHY_OP_TYPE_REORDER &&
+ hop.container == wallpaperToken.asBinder() &&
+ !hop.toTop
+ }
+ finishWct.assertWithoutHop { hop -> hop.type == HIERARCHY_OP_TYPE_PENDING_INTENT }
+ }
+
private class RunOnStartTransitionCallback : ((IBinder) -> Unit) {
var invocations = 0
private set
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
index 409ca57715fc..e55d7cbb73e1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -264,6 +264,36 @@ class DesksTransitionObserverTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionFinish_deactivateDesk_updatesRepository() {
+ val transition = Binder()
+ val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(deactivateTransition)
+ observer.onTransitionFinished(transition)
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionMergedAndFinished_deactivateDesk_updatesRepository() {
+ val transition = Binder()
+ val deactivateTransition = DeskTransition.DeactivateDesk(transition, deskId = 5)
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+ repository.setActiveDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(deactivateTransition)
+ val bookEndTransition = Binder()
+ observer.onTransitionMerged(merged = transition, playing = bookEndTransition)
+ observer.onTransitionFinished(bookEndTransition)
+
+ assertThat(repository.getActiveDeskId(DEFAULT_DISPLAY)).isNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
fun onTransitionReady_twoPendingTransitions_handlesBoth() {
val transition = Binder()
// Active one desk and deactivate another in different displays, such as in some
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
index 714e5f486285..69a42164071b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskTransitionObserverTest.java
@@ -373,6 +373,25 @@ public class FreeformTaskTransitionObserverTest extends ShellTestCase {
verify(mDesksTransitionObserver).onTransitionReady(transition, info);
}
+ @Test
+ public void onTransitionMerged_forwardsToDesksTransitionObserver() {
+ final IBinder merged = mock(IBinder.class);
+ final IBinder playing = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionMerged(merged, playing);
+
+ verify(mDesksTransitionObserver).onTransitionMerged(merged, playing);
+ }
+
+ @Test
+ public void onTransitionFinished_forwardsToDesksTransitionObserver() {
+ final IBinder transition = mock(IBinder.class);
+
+ mTransitionObserver.onTransitionFinished(transition, /* aborted = */ false);
+
+ verify(mDesksTransitionObserver).onTransitionFinished(transition);
+ }
+
private static TransitionInfo.Change createChange(int mode, int taskId, int windowingMode) {
final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
taskInfo.taskId = taskId;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
index 4c9c2f14d805..2126d1d9b986 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTestsBase.kt
@@ -55,6 +55,7 @@ import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.MultiDisplayDragMoveIndicatorController
import com.android.wm.shell.common.MultiInstanceHelper
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.compatui.api.CompatUIHandler
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler
import com.android.wm.shell.desktopmode.DesktopImmersiveController
import com.android.wm.shell.desktopmode.DesktopModeEventLogger
@@ -144,6 +145,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mock<DesktopActivityOrientationChangeHandler>()
protected val mockMultiDisplayDragMoveIndicatorController =
mock<MultiDisplayDragMoveIndicatorController>()
+ protected val mockCompatUIHandler = mock<CompatUIHandler>()
protected val mockInputManager = mock<InputManager>()
private val mockTaskPositionerFactory =
mock<DesktopModeWindowDecorViewModel.TaskPositionerFactory>()
@@ -243,6 +245,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
desktopModeCompatPolicy,
mockTilingWindowDecoration,
mockMultiDisplayDragMoveIndicatorController,
+ mockCompatUIHandler,
)
desktopModeWindowDecorViewModel.setSplitScreenController(mockSplitScreenController)
whenever(mockDisplayController.getDisplayLayout(any())).thenReturn(mockDisplayLayout)
@@ -334,7 +337,7 @@ open class DesktopModeWindowDecorViewModelTestsBase : ShellTestCase() {
mockDesktopModeWindowDecorFactory.create(
any(), any(), any(), any(), any(), any(), any(), eq(task), any(), any(), any(),
any(), any(), any(), any(), any(), any(), any(), any(), any(), any(), any(),
- any(), any(), any(), any())
+ any(), any(), any(), any(), any())
).thenReturn(decoration)
decoration.mTaskInfo = task
whenever(decoration.user).thenReturn(mockUserHandle)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index f7b9c3352dea..0908f56e1cfb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -106,6 +106,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.desktopmode.CaptionState;
import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.desktopmode.DesktopUserRepositories;
import com.android.wm.shell.desktopmode.WindowDecorCaptionHandleRepository;
@@ -246,6 +247,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Mock
private DesktopModeEventLogger mDesktopModeEventLogger;
@Mock
+ private DesktopModeUiEventLogger mDesktopModeUiEventLogger;
+ @Mock
private DesktopRepository mDesktopRepository;
@Mock
private WindowDecorTaskResourceLoader mMockTaskResourceLoader;
@@ -303,15 +306,15 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
doReturn(mInsetsState).when(mMockDisplayController).getInsetsState(anyInt());
when(mMockHandleMenuFactory.create(any(), any(), any(), any(), any(), anyInt(), any(),
anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
- any(), anyInt(), anyInt(), anyInt(), anyInt()))
+ anyBoolean(), any(), any(), anyInt(), anyInt(), anyInt(), anyInt()))
.thenReturn(mMockHandleMenu);
when(mMockMultiInstanceHelper.supportsMultiInstanceSplit(any(), anyInt()))
.thenReturn(false);
when(mMockAppHeaderViewHolderFactory
- .create(any(), any(), any(), any(), any(), any(), any(), any(), any()))
+ .create(any(), any(), any(), any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mMockAppHeaderViewHolder);
when(mMockAppHandleViewHolderFactory
- .create(any(), any(), any(), any(), any()))
+ .create(any(), any(), any(), any(), any(), any()))
.thenReturn(mMockAppHandleViewHolder);
when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mDesktopRepository);
when(mMockDesktopUserRepositories.getProfile(anyInt())).thenReturn(mDesktopRepository);
@@ -1450,6 +1453,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
anyBoolean()
);
// Run runnable to set captured link to used
@@ -1487,6 +1491,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
anyBoolean()
);
openInBrowserCaptor.getValue().invoke(new Intent(Intent.ACTION_MAIN, TEST_URI1));
@@ -1519,6 +1524,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
anyBoolean()
);
@@ -1584,6 +1590,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
closeClickListener.capture(),
any(),
anyBoolean()
@@ -1617,6 +1624,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
any(),
any(),
any(),
+ any(),
/* forceShowSystemBars= */ eq(true)
);
}
@@ -1790,9 +1798,9 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
private void verifyHandleMenuCreated(@Nullable Uri uri) {
verify(mMockHandleMenuFactory).create(any(), any(), any(), any(), any(), anyInt(),
any(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(), anyBoolean(),
- anyBoolean(), argThat(intent ->
+ anyBoolean(), anyBoolean(), argThat(intent ->
(uri == null && intent == null) || intent.getData().equals(uri)),
- anyInt(), anyInt(), anyInt(), anyInt());
+ any(), anyInt(), anyInt(), anyInt(), anyInt());
}
private void createMaximizeMenu(DesktopModeWindowDecoration decoration) {
@@ -1889,7 +1897,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
new WindowManagerWrapper(mMockWindowManager), mMockSurfaceControlViewHostFactory,
mMockWindowDecorViewHostSupplier, maximizeMenuFactory, mMockHandleMenuFactory,
mMockMultiInstanceHelper, mMockCaptionHandleRepository, mDesktopModeEventLogger,
- mDesktopModeCompatPolicy);
+ mDesktopModeUiEventLogger, mDesktopModeCompatPolicy);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
@@ -1916,6 +1924,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
.setTaskDescriptionBuilder(taskDescriptionBuilder)
.setVisible(visible)
.build();
+ taskInfo.isVisibleRequested = visible;
taskInfo.realActivity = new ComponentName("com.android.wm.shell.windowdecor",
"DesktopModeWindowDecorationTests");
taskInfo.baseActivity = new ComponentName("com.android.wm.shell.windowdecor",
@@ -2014,7 +2023,8 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@NonNull Context decorWindowContext,
@NonNull Function2<? super Integer,? super Integer,? extends Point>
positionSupplier,
- @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier,
+ @NonNull DesktopModeUiEventLogger desktopModeUiEventLogger) {
return mMaximizeMenu;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index 2e46f6312d03..b0b95c97ae18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -43,6 +43,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
@@ -95,6 +96,8 @@ class HandleMenuTest : ShellTestCase() {
private lateinit var mockTaskResourceLoader: WindowDecorTaskResourceLoader
@Mock
private lateinit var mockAppIcon: Bitmap
+ @Mock
+ private lateinit var mockDesktopModeUiEventLogger: DesktopModeUiEventLogger
private lateinit var handleMenu: HandleMenu
@@ -287,8 +290,10 @@ class HandleMenuTest : ShellTestCase() {
shouldShowManageWindowsButton = false,
shouldShowChangeAspectRatioButton = false,
shouldShowDesktopModeButton = true,
+ shouldShowRestartButton = true,
isBrowserApp = false,
null /* openInAppOrBrowserIntent */,
+ mockDesktopModeUiEventLogger,
captionWidth = HANDLE_WIDTH,
captionHeight = 50,
captionX = captionX,
@@ -304,6 +309,7 @@ class HandleMenuTest : ShellTestCase() {
onChangeAspectRatioClickListener = mock(),
openInAppOrBrowserClickListener = mock(),
onOpenByDefaultClickListener = mock(),
+ onRestartClickListener = mock(),
onCloseMenuClickListener = mock(),
onOutsideTouchListener = mock(),
forceShowSystemBars = forceShowSystemBars
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index e5f8d7d34a47..0adca5e864bf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -587,6 +587,31 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun tilingDivider_shouldBeShown_whenTiledTasksBecomeVisible() {
+ val task1 = createVisibleTask()
+ val task2 = createVisibleTask()
+ val additionalTaskHelper: DesktopTilingWindowDecoration.AppResizingHelper = mock()
+ whenever(tiledTaskHelper.taskInfo).thenReturn(task1)
+ whenever(tiledTaskHelper.desktopModeWindowDecoration).thenReturn(desktopWindowDecoration)
+ whenever(additionalTaskHelper.taskInfo).thenReturn(task2)
+ whenever(additionalTaskHelper.desktopModeWindowDecoration)
+ .thenReturn(desktopWindowDecoration)
+
+ tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+ tilingDecoration.rightTaskResizingHelper = additionalTaskHelper
+ tilingDecoration.desktopTilingDividerWindowManager = desktopTilingDividerWindowManager
+ val changeInfo = createTransitFrontTransition(task1, task2)
+ tilingDecoration.onTransitionReady(
+ transition = mock(),
+ info = changeInfo,
+ startTransaction = mock(),
+ finishTransaction = mock(),
+ )
+
+ verify(desktopTilingDividerWindowManager, times(1)).showDividerBar()
+ }
+
+ @Test
fun taskNotTiled_shouldNotBeRemoved_whenNotTiled() {
val task1 = createVisibleTask()
val task2 = createVisibleTask()
@@ -762,6 +787,30 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
)
}
+ private fun createTransitFrontTransition(
+ task1: RunningTaskInfo?,
+ task2: RunningTaskInfo?,
+ type: Int = TRANSIT_TO_FRONT,
+ ) =
+ TransitionInfo(type, /* flags= */ 0).apply {
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_TO_FRONT
+ parent = null
+ taskInfo = task1
+ flags = flags
+ }
+ )
+ addChange(
+ Change(mock(), mock()).apply {
+ mode = TRANSIT_TO_FRONT
+ parent = null
+ taskInfo = task2
+ flags = flags
+ }
+ )
+ }
+
companion object {
private val NON_STABLE_BOUNDS_MOCK = Rect(50, 55, 100, 100)
private val STABLE_BOUNDS_MOCK = Rect(0, 0, 100, 100)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolderTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolderTest.kt
index 2c3009cb8dc4..e23019a59307 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolderTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolderTest.kt
@@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
import com.android.wm.shell.R
import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.desktopmode.DesktopModeUiEventLogger
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import org.junit.Before
import org.junit.runner.RunWith
@@ -57,6 +58,7 @@ class AppHandleViewHolderTest : ShellTestCase() {
private val mockWindowManagerWrapper = mock<WindowManagerWrapper>()
private val mockHandler = mock<Handler>()
private val mockTaskInfo = mock<RunningTaskInfo>()
+ private val mockDesktopModeUiEventLogger = mock<DesktopModeUiEventLogger>()
@Before
fun setup() {
@@ -92,7 +94,8 @@ class AppHandleViewHolderTest : ShellTestCase() {
mockOnTouchListener,
mockOnClickListener,
mockWindowManagerWrapper,
- mockHandler
+ mockHandler,
+ mockDesktopModeUiEventLogger,
)
}
}
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 714f6e41ff05..e09ab5fd1643 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -879,10 +879,10 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntry(
// if we don't have a result yet
if (!final_result ||
// or this config is better before the locale than the existing result
- result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
+ result->config.isBetterThanBeforeLocale(final_result->config, *desired_config) ||
// or the existing config isn't better before locale and this one specifies a locale
// whereas the existing one doesn't
- (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
+ (!final_result->config.isBetterThanBeforeLocale(result->config, *desired_config)
&& has_locale && !final_has_locale)) {
final_result = result.value();
final_overlaid = overlaid;
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 8ecd6ba9b253..6ec605c2ced5 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2615,16 +2615,14 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
}
bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
- const ResTable_config* requested) const {
- if (requested) {
- if (imsi || o.imsi) {
- if ((mcc != o.mcc) && requested->mcc) {
- return (mcc);
- }
+ const ResTable_config& requested) const {
+ if (imsi || o.imsi) {
+ if ((mcc != o.mcc) && requested.mcc) {
+ return mcc;
+ }
- if ((mnc != o.mnc) && requested->mnc) {
- return (mnc);
- }
+ if ((mnc != o.mnc) && requested.mnc) {
+ return mnc;
}
}
return false;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 63b28da075cd..bd72d3741460 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1416,7 +1416,10 @@ struct ResTable_config
// match the requested configuration at all.
bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
- bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
+ // The first part of isBetterThan() that only compares the fields that are higher priority than
+ // the locale. Use it when you need to do custom locale matching to filter out the configs prior
+ // to that.
+ bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config& requested) const;
String8 toString() const;
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 104ece6582f5..b6a2ad7064a9 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -28,7 +28,9 @@
#include "SkRefCnt.h"
#include "SkStream.h"
#include "SkTypes.h"
+#ifdef __linux__ // Only Linux support parcel
#include "android/binder_parcel.h"
+#endif
#include "android_nio_utils.h"
#ifdef __ANDROID__
@@ -851,6 +853,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) {
#endif
}
+#ifdef __linux__ // Only Linux support parcel
// Returns whether this bitmap should be written to the parcel as mutable.
static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
// If the bitmap is immutable, then parcel as immutable.
@@ -867,6 +870,7 @@ static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
// writing it to the parcel.
return !shouldUseAshmem(parcel, bitmap.computeByteSize());
}
+#endif
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, jlong bitmapHandle, jint density,
jobject parcel) {
diff --git a/libs/hwui/jni/GIFMovie.cpp b/libs/hwui/jni/GIFMovie.cpp
index 476b6fda5007..6c82aa1ca27d 100644
--- a/libs/hwui/jni/GIFMovie.cpp
+++ b/libs/hwui/jni/GIFMovie.cpp
@@ -63,7 +63,7 @@ GIFMovie::GIFMovie(SkStream* stream)
}
fCurrIndex = -1;
fLastDrawIndex = -1;
- fPaintingColor = SK_AlphaTRANSPARENT;
+ fPaintingColor = SkPackARGB32(0, 0, 0, 0);
}
GIFMovie::~GIFMovie()
@@ -127,7 +127,7 @@ static void copyLine(uint32_t* dst, const unsigned char* src, const ColorMapObje
for (; width > 0; width--, src++, dst++) {
if (*src != transparent && *src < cmap->ColorCount) {
const GifColorType& col = cmap->Colors[*src];
- *dst = SkColorSetRGB(col.Red, col.Green, col.Blue);
+ *dst = SkPackARGB32(0xFF, col.Red, col.Green, col.Blue);
}
}
}
@@ -395,10 +395,10 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm)
lastIndex = fGIF->ImageCount - 1;
}
- SkColor bgColor = SK_ColorTRANSPARENT;
+ SkColor bgColor = SkPackARGB32(0, 0, 0, 0);
if (gif->SColorMap != nullptr && gif->SBackGroundColor < gif->SColorMap->ColorCount) {
const GifColorType& col = gif->SColorMap->Colors[gif->SBackGroundColor];
- bgColor = SkColorSetRGB(col.Red, col.Green, col.Blue);
+ bgColor = SkColorSetARGB(0xFF, col.Red, col.Green, col.Blue);
}
// draw each frames - not intelligent way
@@ -411,7 +411,7 @@ bool GIFMovie::onGetBitmap(SkBitmap* bm)
if (!trans && gif->SColorMap != nullptr) {
fPaintingColor = bgColor;
} else {
- fPaintingColor = SK_ColorTRANSPARENT;
+ fPaintingColor = SkColorSetARGB(0, 0, 0, 0);
}
bm->eraseColor(fPaintingColor);
diff --git a/libs/hwui/renderthread/ReliableSurface.cpp b/libs/hwui/renderthread/ReliableSurface.cpp
index 01e8010444c0..64d38b9ef466 100644
--- a/libs/hwui/renderthread/ReliableSurface.cpp
+++ b/libs/hwui/renderthread/ReliableSurface.cpp
@@ -149,25 +149,9 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) {
return AHardwareBuffer_to_ANativeWindowBuffer(mScratchBuffer.get());
}
- int width = -1;
- int result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
- if (result != OK || width < 0) {
- ALOGW("Failed to query window default width: %s (%d) value=%d", strerror(-result), result,
- width);
- width = 1;
- }
-
- int height = -1;
- result = mWindow->query(mWindow, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
- if (result != OK || height < 0) {
- ALOGW("Failed to query window default height: %s (%d) value=%d", strerror(-result), result,
- height);
- height = 1;
- }
-
AHardwareBuffer_Desc desc = AHardwareBuffer_Desc{
- .width = static_cast<uint32_t>(width),
- .height = static_cast<uint32_t>(height),
+ .width = 1,
+ .height = 1,
.layers = 1,
.format = mFormat,
.usage = mUsage,
@@ -176,9 +160,9 @@ ANativeWindowBuffer* ReliableSurface::acquireFallbackBuffer(int error) {
};
AHardwareBuffer* newBuffer;
- result = AHardwareBuffer_allocate(&desc, &newBuffer);
+ int result = AHardwareBuffer_allocate(&desc, &newBuffer);
- if (result != OK) {
+ if (result != NO_ERROR) {
// Allocate failed, that sucks
ALOGW("Failed to allocate scratch buffer, error=%d", result);
return nullptr;
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index a67aea466c1c..e5a6260cfd98 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -44,7 +44,7 @@ namespace uirenderer {
namespace renderthread {
// Not all of these are strictly required, but are all enabled if present.
-static std::array<std::string_view, 23> sEnableExtensions{
+static std::array<std::string_view, 25> sEnableExtensions{
VK_KHR_BIND_MEMORY_2_EXTENSION_NAME,
VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME,
@@ -68,6 +68,8 @@ static std::array<std::string_view, 23> sEnableExtensions{
VK_EXT_GLOBAL_PRIORITY_QUERY_EXTENSION_NAME,
VK_KHR_GLOBAL_PRIORITY_EXTENSION_NAME,
VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
+ VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME,
+ VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME,
};
static bool shouldEnableExtension(const std::string_view& extension) {
@@ -238,6 +240,7 @@ void VulkanManager::setupDevice(skgpu::VulkanExtensions& grExtensions,
for (uint32_t i = 0; i < queueCount; i++) {
queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
queuePriorityProps[i].pNext = nullptr;
+ queueProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
queueProps[i].pNext = &queuePriorityProps[i];
}
mGetPhysicalDeviceQueueFamilyProperties2(mPhysicalDevice, &queueCount, queueProps.get());
@@ -745,7 +748,14 @@ VulkanManager::VkDrawResult VulkanManager::finishFrame(SkSurface* surface) {
ALOGE_IF(!context, "Surface is not backed by gpu");
GrSemaphoresSubmitted submitted = context->flush(
surface, SkSurfaces::BackendSurfaceAccess::kPresent, flushInfo);
- context->submit();
+
+ static uint64_t currentFrameID = 0;
+ GrSubmitInfo submitInfo;
+ if (!mFrameBoundaryANDROID) {
+ submitInfo.fMarkBoundary = GrMarkFrameBoundary::kYes;
+ submitInfo.fFrameID = currentFrameID++;
+ }
+ context->submit(submitInfo);
VkDrawResult drawResult{
.submissionTime = systemTime(),
};
diff --git a/libs/hwui/renderthread/VulkanManager.h b/libs/hwui/renderthread/VulkanManager.h
index a593ec6f8351..744211e39f79 100644
--- a/libs/hwui/renderthread/VulkanManager.h
+++ b/libs/hwui/renderthread/VulkanManager.h
@@ -30,14 +30,10 @@
// VK_ANDROID_frame_boundary is a bespoke extension defined by AGI
// (https://github.com/google/agi) to enable profiling of apps rendering via
// HWUI. This extension is not defined in Khronos, hence the need to declare it
-// manually here. There's a superseding extension (VK_EXT_frame_boundary) being
-// discussed in Khronos, but in the meantime we use the bespoke
-// VK_ANDROID_frame_boundary. This is a device extension that is implemented by
+// manually here. There's an extension (VK_EXT_frame_boundary) which we will use
+// instead if available. This is a device extension that is implemented by
// AGI's Vulkan capture layer, such that it is only supported by devices when
// AGI is doing a capture of the app.
-//
-// TODO(b/182165045): use the Khronos blessed VK_EXT_frame_boudary once it has
-// landed in the spec.
typedef void(VKAPI_PTR* PFN_vkFrameBoundaryANDROID)(VkDevice device, VkSemaphore semaphore,
VkImage image);
#define VK_ANDROID_FRAME_BOUNDARY_EXTENSION_NAME "VK_ANDROID_frame_boundary"
diff --git a/location/api/system-current.txt b/location/api/system-current.txt
index eb19ba84ee62..47984745fafc 100644
--- a/location/api/system-current.txt
+++ b/location/api/system-current.txt
@@ -96,8 +96,8 @@ package android.location {
public static final class BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0, to=31) public int getAode();
method @IntRange(from=0) public int getBeidouWeekNumber();
- method @IntRange(from=0, to=31) public int getIode();
method @IntRange(from=0, to=604792) public int getToeSeconds();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime> CREATOR;
@@ -106,8 +106,8 @@ package android.location {
public static final class BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder {
ctor public BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder();
method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime build();
+ method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setAode(int);
method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setBeidouWeekNumber(@IntRange(from=0) int);
- method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setIode(int);
method @NonNull public android.location.BeidouSatelliteEphemeris.BeidouSatelliteEphemerisTime.Builder setToeSeconds(@IntRange(from=0, to=604792) int);
}
@@ -177,7 +177,7 @@ package android.location {
method public int describeContents();
method @Nullable public android.location.GnssAlmanac getAlmanac();
method @Nullable public android.location.AuxiliaryInformation getAuxiliaryInformation();
- method @Nullable public android.location.KlobucharIonosphericModel getIonosphericModel();
+ method @Nullable public android.location.GalileoIonosphericModel getIonosphericModel();
method @Nullable public android.location.LeapSecondsModel getLeapSecondsModel();
method @NonNull public java.util.List<android.location.RealTimeIntegrityModel> getRealTimeIntegrityModels();
method @NonNull public java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections> getSatelliteCorrections();
@@ -193,7 +193,7 @@ package android.location {
method @NonNull public android.location.GalileoAssistance build();
method @NonNull public android.location.GalileoAssistance.Builder setAlmanac(@Nullable android.location.GnssAlmanac);
method @NonNull public android.location.GalileoAssistance.Builder setAuxiliaryInformation(@Nullable android.location.AuxiliaryInformation);
- method @NonNull public android.location.GalileoAssistance.Builder setIonosphericModel(@Nullable android.location.KlobucharIonosphericModel);
+ method @NonNull public android.location.GalileoAssistance.Builder setIonosphericModel(@Nullable android.location.GalileoIonosphericModel);
method @NonNull public android.location.GalileoAssistance.Builder setLeapSecondsModel(@Nullable android.location.LeapSecondsModel);
method @NonNull public android.location.GalileoAssistance.Builder setRealTimeIntegrityModels(@NonNull java.util.List<android.location.RealTimeIntegrityModel>);
method @NonNull public android.location.GalileoAssistance.Builder setSatelliteCorrections(@NonNull java.util.List<android.location.GnssAssistance.GnssSatelliteCorrections>);
diff --git a/location/java/android/location/BeidouSatelliteEphemeris.java b/location/java/android/location/BeidouSatelliteEphemeris.java
index 3382c20964d9..dc5e8b89d7d7 100644
--- a/location/java/android/location/BeidouSatelliteEphemeris.java
+++ b/location/java/android/location/BeidouSatelliteEphemeris.java
@@ -527,7 +527,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
*
* <p>This is defined in BDS-SIS-ICD-B1I-3.0 section 5.2.4.11 Table 5-8.
*/
- private final int mIode;
+ private final int mAode;
/** Beidou week number without rollover */
private final int mBeidouWeekNumber;
@@ -540,18 +540,18 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
private final int mToeSeconds;
private BeidouSatelliteEphemerisTime(Builder builder) {
- Preconditions.checkArgumentInRange(builder.mIode, 0, 31, "Iode");
+ Preconditions.checkArgumentInRange(builder.mAode, 0, 31, "Aode");
Preconditions.checkArgument(builder.mBeidouWeekNumber >= 0);
Preconditions.checkArgumentInRange(builder.mToeSeconds, 0, 604792, "ToeSeconds");
- mIode = builder.mIode;
+ mAode = builder.mAode;
mBeidouWeekNumber = builder.mBeidouWeekNumber;
mToeSeconds = builder.mToeSeconds;
}
/** Returns the AODE Age of Data, Ephemeris. */
@IntRange(from = 0, to = 31)
- public int getIode() {
- return mIode;
+ public int getAode() {
+ return mAode;
}
/** Returns the Beidou week number without rollover . */
@@ -573,7 +573,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
public BeidouSatelliteEphemerisTime createFromParcel(Parcel in) {
final BeidouSatelliteEphemerisTime.Builder beidouSatelliteEphemerisTime =
new Builder()
- .setIode(in.readInt())
+ .setAode(in.readInt())
.setBeidouWeekNumber(in.readInt())
.setToeSeconds(in.readInt());
return beidouSatelliteEphemerisTime.build();
@@ -592,7 +592,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
@Override
public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeInt(mIode);
+ parcel.writeInt(mAode);
parcel.writeInt(mBeidouWeekNumber);
parcel.writeInt(mToeSeconds);
}
@@ -600,7 +600,7 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
@Override
public String toString() {
StringBuilder builder = new StringBuilder("BeidouSatelliteEphemerisTime[");
- builder.append("iode = ").append(mIode);
+ builder.append("aode = ").append(mAode);
builder.append(", beidouWeekNumber = ").append(mBeidouWeekNumber);
builder.append(", toeSeconds = ").append(mToeSeconds);
builder.append("]");
@@ -609,14 +609,14 @@ public final class BeidouSatelliteEphemeris implements Parcelable {
/** Builder for {@link BeidouSatelliteEphemerisTime} */
public static final class Builder {
- private int mIode;
+ private int mAode;
private int mBeidouWeekNumber;
private int mToeSeconds;
/** Sets the AODE Age of Data, Ephemeris. */
@NonNull
- public Builder setIode(int iode) {
- mIode = iode;
+ public Builder setAode(int iode) {
+ mAode = iode;
return this;
}
diff --git a/location/java/android/location/GalileoAssistance.java b/location/java/android/location/GalileoAssistance.java
index 8a09e6634d09..7f81ccdf346f 100644
--- a/location/java/android/location/GalileoAssistance.java
+++ b/location/java/android/location/GalileoAssistance.java
@@ -41,8 +41,8 @@ public final class GalileoAssistance implements Parcelable {
/** The Galileo almanac. */
@Nullable private final GnssAlmanac mAlmanac;
- /** The Klobuchar ionospheric model. */
- @Nullable private final KlobucharIonosphericModel mIonosphericModel;
+ /** The Galileo ionospheric model. */
+ @Nullable private final GalileoIonosphericModel mIonosphericModel;
/** The UTC model. */
@Nullable private final UtcModel mUtcModel;
@@ -102,9 +102,9 @@ public final class GalileoAssistance implements Parcelable {
return mAlmanac;
}
- /** Returns the Klobuchar ionospheric model. */
+ /** Returns the Galileo ionospheric model. */
@Nullable
- public KlobucharIonosphericModel getIonosphericModel() {
+ public GalileoIonosphericModel getIonosphericModel() {
return mIonosphericModel;
}
@@ -192,7 +192,7 @@ public final class GalileoAssistance implements Parcelable {
return new GalileoAssistance.Builder()
.setAlmanac(in.readTypedObject(GnssAlmanac.CREATOR))
.setIonosphericModel(
- in.readTypedObject(KlobucharIonosphericModel.CREATOR))
+ in.readTypedObject(GalileoIonosphericModel.CREATOR))
.setUtcModel(in.readTypedObject(UtcModel.CREATOR))
.setLeapSecondsModel(in.readTypedObject(LeapSecondsModel.CREATOR))
.setAuxiliaryInformation(
@@ -216,7 +216,7 @@ public final class GalileoAssistance implements Parcelable {
/** Builder for {@link GalileoAssistance}. */
public static final class Builder {
private GnssAlmanac mAlmanac;
- private KlobucharIonosphericModel mIonosphericModel;
+ private GalileoIonosphericModel mIonosphericModel;
private UtcModel mUtcModel;
private LeapSecondsModel mLeapSecondsModel;
private AuxiliaryInformation mAuxiliaryInformation;
@@ -232,9 +232,9 @@ public final class GalileoAssistance implements Parcelable {
return this;
}
- /** Sets the Klobuchar ionospheric model. */
+ /** Sets the Galileo ionospheric model. */
@NonNull
- public Builder setIonosphericModel(@Nullable KlobucharIonosphericModel ionosphericModel) {
+ public Builder setIonosphericModel(@Nullable GalileoIonosphericModel ionosphericModel) {
mIonosphericModel = ionosphericModel;
return this;
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 4aba491c291e..0a1bfd55e77f 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -19,14 +19,15 @@ package android.media;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.content.Context.DEVICE_ID_DEFAULT;
-import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;
-import static android.media.audio.Flags.autoPublicVolumeApiHardening;
-import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
-import static android.media.audio.Flags.cacheGetStreamVolume;
import static android.media.audio.Flags.FLAG_DEPRECATE_STREAM_BT_SCO;
import static android.media.audio.Flags.FLAG_FOCUS_EXCLUSIVE_WITH_RECORDING;
import static android.media.audio.Flags.FLAG_FOCUS_FREEZE_TEST_API;
+import static android.media.audio.Flags.FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING;
import static android.media.audio.Flags.FLAG_SUPPORTED_DEVICE_TYPES_API;
+import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;
+import static android.media.audio.Flags.autoPublicVolumeApiHardening;
+import static android.media.audio.Flags.cacheGetStreamMinMaxVolume;
+import static android.media.audio.Flags.cacheGetStreamVolume;
import static android.media.audiopolicy.Flags.FLAG_ENABLE_FADE_MANAGER_CONFIGURATION;
import android.Manifest;
@@ -65,7 +66,7 @@ import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.MediaProjection;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -128,8 +129,6 @@ public class AudioManager {
private static final String TAG = "AudioManager";
private static final boolean DEBUG = false;
private static final AudioPortEventHandler sAudioPortEventHandler = new AudioPortEventHandler();
- private static final AudioVolumeGroupChangeHandler sAudioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
private static WeakReference<Context> sContext;
@@ -8761,9 +8760,13 @@ public class AudioManager {
}
}
+ //====================================================================
+ // Notification of volume group changes
/**
+ * Callback to receive updates on volume group changes, register using
+ * {@link AudioManager#registerVolumeGroupCallback(Executor, AudioVolumeCallback)}.
+ *
* @hide
- * Callback registered by client to be notified upon volume group change.
*/
@SystemApi
public abstract static class VolumeGroupCallback {
@@ -8774,34 +8777,69 @@ public class AudioManager {
public void onAudioVolumeGroupChanged(int group, int flags) {}
}
- /**
- * @hide
- * Register an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to register
- */
+ /**
+ * Register an audio volume group change listener.
+ *
+ * @param executor {@link Executor} to handle the callbacks
+ * @param callback the callback to receive the audio volume group changes
+ * @throws SecurityException if the caller doesn't have the required permission.
+ *
+ * @hide
+ */
@SystemApi
- public void registerVolumeGroupCallback(
- @NonNull Executor executor,
+ @FlaggedApi(FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING)
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public void registerVolumeGroupCallback(@NonNull Executor executor,
@NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(executor, "executor must not be null");
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.init();
- // TODO: make use of executor
- sAudioAudioVolumeGroupChangedHandler.registerListener(callback);
+ mVolumeChangedListenerMgr.addListener(executor, callback, "registerVolumeGroupCallback",
+ () -> new AudioVolumeChangeDispatcherStub());
}
- /**
- * @hide
- * Unregister an audio volume group change listener.
- * @param callback the {@link VolumeGroupCallback} to unregister
- */
+ /**
+ * Unregister an audio volume group change listener.
+ *
+ * @param callback the {@link VolumeGroupCallback} to unregister
+ *
+ * @hide
+ */
@SystemApi
- public void unregisterVolumeGroupCallback(
- @NonNull VolumeGroupCallback callback) {
- Preconditions.checkNotNull(callback, "volume group change cb must not be null");
- sAudioAudioVolumeGroupChangedHandler.unregisterListener(callback);
+ @FlaggedApi(FLAG_REGISTER_VOLUME_CALLBACK_API_HARDENING)
+ @RequiresPermission("Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED")
+ public void unregisterVolumeGroupCallback(@NonNull VolumeGroupCallback callback) {
+ mVolumeChangedListenerMgr.removeListener(callback, "unregisterVolumeGroupCallback");
+ }
+
+ /**
+ * Manages the VolumeGroupCallback listeners and the AudioVolumeChangeDispatcherStub
+ */
+ private final CallbackUtil.LazyListenerManager<VolumeGroupCallback> mVolumeChangedListenerMgr =
+ new CallbackUtil.LazyListenerManager();
+
+ final class AudioVolumeChangeDispatcherStub extends IAudioVolumeChangeDispatcher.Stub
+ implements CallbackUtil.DispatcherStub {
+
+ @Override
+ public void register(boolean register) {
+ try {
+ if (register) {
+ getService().registerAudioVolumeCallback(this);
+ } else {
+ getService().unregisterAudioVolumeCallback(this);
+ }
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void onAudioVolumeGroupChanged(int group, int flags) {
+ mVolumeChangedListenerMgr.callListeners((listener) ->
+ listener.onAudioVolumeGroupChanged(group, flags));
+ }
}
+ //====================================================================
+
/**
* Return if an asset contains haptic channels or not.
*
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index ad6f2e52fd97..4906cd3fb1e5 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2732,4 +2732,25 @@ public class AudioSystem
* @hide
*/
public static native void triggerSystemPropertyUpdate(long handle);
+
+ /**
+ * Registers the given {@link INativeAudioVolumeGroupCallback} to native audioserver.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int registerAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
+
+ /**
+ * Unegisters the given {@link INativeAudioVolumeGroupCallback} from native audioserver
+ * previously registered via {@link #registerAudioVolumeGroupCallback}.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public static native int unregisterAudioVolumeGroupCallback(
+ INativeAudioVolumeGroupCallback callback);
}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 8aadb418cf5a..b97b943113b6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -65,6 +65,7 @@ import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.projection.IMediaProjection;
import android.net.Uri;
import android.os.PersistableBundle;
@@ -446,6 +447,10 @@ interface IAudioService {
boolean isAudioServerRunning();
+ void registerAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
+ oneway void unregisterAudioVolumeCallback(IAudioVolumeChangeDispatcher avc);
+
int setUidDeviceAffinity(in IAudioPolicyCallback pcb, in int uid, in int[] deviceTypes,
in String[] deviceAddresses);
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java b/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
deleted file mode 100644
index 022cfeeb4e43..000000000000
--- a/media/java/android/media/audiopolicy/AudioVolumeGroupChangeHandler.java
+++ /dev/null
@@ -1,166 +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 android.media.audiopolicy;
-
-import android.annotation.NonNull;
-import android.media.AudioManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-
-import com.android.internal.util.Preconditions;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-
-/**
- * The AudioVolumeGroupChangeHandler handles AudioManager.OnAudioVolumeGroupChangedListener
- * callbacks posted from JNI
- *
- * TODO: Make use of Executor of callbacks.
- * @hide
- */
-public class AudioVolumeGroupChangeHandler {
- private Handler mHandler;
- private HandlerThread mHandlerThread;
- private final ArrayList<AudioManager.VolumeGroupCallback> mListeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
-
- private static final String TAG = "AudioVolumeGroupChangeHandler";
-
- private static final int AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED = 1000;
- private static final int AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER = 4;
-
- /**
- * Accessed by native methods: JNI Callback context.
- */
- @SuppressWarnings("unused")
- private long mJniCallback;
-
- /**
- * Initialization
- */
- public void init() {
- synchronized (this) {
- if (mHandler != null) {
- return;
- }
- // create a new thread for our new event handler
- mHandlerThread = new HandlerThread(TAG);
- mHandlerThread.start();
-
- if (mHandlerThread.getLooper() == null) {
- mHandler = null;
- return;
- }
- mHandler = new Handler(mHandlerThread.getLooper()) {
- @Override
- public void handleMessage(Message msg) {
- ArrayList<AudioManager.VolumeGroupCallback> listeners;
- synchronized (this) {
- if (msg.what == AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER) {
- listeners =
- new ArrayList<AudioManager.VolumeGroupCallback>();
- if (mListeners.contains(msg.obj)) {
- listeners.add(
- (AudioManager.VolumeGroupCallback) msg.obj);
- }
- } else {
- listeners = (ArrayList<AudioManager.VolumeGroupCallback>)
- mListeners.clone();
- }
- }
- if (listeners.isEmpty()) {
- return;
- }
-
- switch (msg.what) {
- case AUDIOVOLUMEGROUP_EVENT_VOLUME_CHANGED:
- for (int i = 0; i < listeners.size(); i++) {
- listeners.get(i).onAudioVolumeGroupChanged((int) msg.arg1,
- (int) msg.arg2);
- }
- break;
-
- default:
- break;
- }
- }
- };
- native_setup(new WeakReference<AudioVolumeGroupChangeHandler>(this));
- }
- }
-
- private native void native_setup(Object moduleThis);
-
- @Override
- protected void finalize() {
- native_finalize();
- if (mHandlerThread.isAlive()) {
- mHandlerThread.quit();
- }
- }
- private native void native_finalize();
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to register
- */
- public void registerListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.add(cb);
- }
- if (mHandler != null) {
- Message m = mHandler.obtainMessage(
- AUDIOVOLUMEGROUP_EVENT_NEW_LISTENER, 0, 0, cb);
- mHandler.sendMessage(m);
- }
- }
-
- /**
- * @param cb the {@link AudioManager.VolumeGroupCallback} to unregister
- */
- public void unregisterListener(@NonNull AudioManager.VolumeGroupCallback cb) {
- Preconditions.checkNotNull(cb, "volume group callback shall not be null");
- synchronized (this) {
- mListeners.remove(cb);
- }
- }
-
- Handler handler() {
- return mHandler;
- }
-
- @SuppressWarnings("unused")
- private static void postEventFromNative(Object moduleRef,
- int what, int arg1, int arg2, Object obj) {
- AudioVolumeGroupChangeHandler eventHandler =
- (AudioVolumeGroupChangeHandler) ((WeakReference) moduleRef).get();
- if (eventHandler == null) {
- return;
- }
-
- if (eventHandler != null) {
- Handler handler = eventHandler.handler();
- if (handler != null) {
- Message m = handler.obtainMessage(what, arg1, arg2, obj);
- // Do not remove previous messages, as we would lose notification of group changes
- handler.sendMessage(m);
- }
- }
- }
-}
diff --git a/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
new file mode 100644
index 000000000000..e6f9024cfd1e
--- /dev/null
+++ b/media/java/android/media/audiopolicy/IAudioVolumeChangeDispatcher.aidl
@@ -0,0 +1,31 @@
+/* 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 android.media.audiopolicy;
+
+/**
+ * AIDL for the AudioService to signal audio volume groups changes
+ *
+ * {@hide}
+ */
+oneway interface IAudioVolumeChangeDispatcher {
+
+ /**
+ * Called when a volume group has been changed
+ * @param group id of the volume group that has changed.
+ * @param flags one or more flags to describe the volume change.
+ */
+ void onAudioVolumeGroupChanged(int group, int flags);
+}
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index b33097c50002..ea62287b7411 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -1,7 +1,7 @@
{
- "presubmit": [
+ "imports": [
{
- "name": "MediaProjectionTests"
+ "path": "frameworks/base/services/core/java/com/android/server/media/projection"
}
]
-}
+} \ No newline at end of file
diff --git a/media/java/android/media/quality/MediaQualityContract.java b/media/java/android/media/quality/MediaQualityContract.java
index fccdba8e727f..ece87a66556f 100644
--- a/media/java/android/media/quality/MediaQualityContract.java
+++ b/media/java/android/media/quality/MediaQualityContract.java
@@ -72,6 +72,43 @@ public class MediaQualityContract {
*/
public static final String LEVEL_OFF = "level_off";
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "COLOR_TEMP", value = {
+ COLOR_TEMP_USER,
+ COLOR_TEMP_COOL,
+ COLOR_TEMP_STANDARD,
+ COLOR_TEMP_WARM,
+ COLOR_TEMP_USER_HDR10PLUS,
+ COLOR_TEMP_COOL_HDR10PLUS,
+ COLOR_TEMP_STANDARD_HDR10PLUS,
+ COLOR_TEMP_WARM_HDR10PLUS,
+ COLOR_TEMP_FMMSDR,
+ COLOR_TEMP_FMMHDR,
+ })
+ public @interface ColorTempValue {}
+
+ /** @hide */
+ public static final String COLOR_TEMP_USER = "color_temp_user";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL = "color_temp_cool";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD = "color_temp_standard";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM = "color_temp_warm";
+ /** @hide */
+ public static final String COLOR_TEMP_USER_HDR10PLUS = "color_temp_user_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_COOL_HDR10PLUS = "color_temp_cool_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_STANDARD_HDR10PLUS = "color_temp_standard_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_WARM_HDR10PLUS = "color_temp_warm_hdr10plus";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMSDR = "color_temp_fmmsdr";
+ /** @hide */
+ public static final String COLOR_TEMP_FMMHDR = "color_temp_fmmhdr";
+
/**
* @hide
@@ -82,7 +119,6 @@ public class MediaQualityContract {
String PARAMETER_NAME = "_name";
String PARAMETER_PACKAGE = "_package";
String PARAMETER_INPUT_ID = "_input_id";
- String VENDOR_PARAMETERS = "_vendor_parameters";
}
/**
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
deleted file mode 100644
index 82394a2eb420..000000000000
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeGroupChangeHandlerTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.audiopolicytest;
-
-import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
-
-import static com.android.audiopolicytest.AudioVolumeTestUtil.DEFAULT_ATTRIBUTES;
-import static com.android.audiopolicytest.AudioVolumeTestUtil.incrementVolumeIndex;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
-
-import android.media.AudioAttributes;
-import android.media.AudioManager;
-import android.media.audiopolicy.AudioVolumeGroup;
-import android.media.audiopolicy.AudioVolumeGroupChangeHandler;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class AudioVolumeGroupChangeHandlerTest {
- private static final String TAG = "AudioVolumeGroupChangeHandlerTest";
-
- @Rule
- public final AudioVolumesTestRule rule = new AudioVolumesTestRule();
-
- private AudioManager mAudioManager;
-
- @Before
- public void setUp() {
- mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
- }
-
- @Test
- public void testRegisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.registerListener(nullCb);
- });
- }
-
- @Test
- public void testUnregisterInvalidCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper cb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
-
- assertThrows(NullPointerException.class, () -> {
- AudioManager.VolumeGroupCallback nullCb = null;
- audioAudioVolumeGroupChangedHandler.unregisterListener(nullCb);
- });
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
-
- @Test
- public void testRegisterUnregisterCallback() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- // Should not assert, otherwise test will fail
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
-
- @Test
- public void testCallbackReceived() {
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final AudioVolumeGroupCallbackHelper validCb = new AudioVolumeGroupCallbackHelper();
- audioAudioVolumeGroupChangedHandler.registerListener(validCb);
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- final AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- validCb.setExpectedVolumeGroup(volumeGroupId);
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
- assertTrue(validCb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
-
- final int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- audioAudioVolumeGroupChangedHandler.unregisterListener(validCb);
- }
- }
-
- @Test
- public void testMultipleCallbackReceived() {
-
- final AudioVolumeGroupChangeHandler audioAudioVolumeGroupChangedHandler =
- new AudioVolumeGroupChangeHandler();
-
- audioAudioVolumeGroupChangedHandler.init();
-
- final int callbackCount = 10;
- final List<AudioVolumeGroupCallbackHelper> validCbs =
- new ArrayList<AudioVolumeGroupCallbackHelper>();
- for (int i = 0; i < callbackCount; i++) {
- validCbs.add(new AudioVolumeGroupCallbackHelper());
- }
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.registerListener(cb);
- }
-
- List<AudioVolumeGroup> audioVolumeGroups = mAudioManager.getAudioVolumeGroups();
- assertTrue(audioVolumeGroups.size() > 0);
-
- try {
- for (final AudioVolumeGroup audioVolumeGroup : audioVolumeGroups) {
- int volumeGroupId = audioVolumeGroup.getId();
-
- List<AudioAttributes> avgAttributes = audioVolumeGroup.getAudioAttributes();
- // Set the volume per attributes (if valid) and wait the callback
- if (avgAttributes.size() == 0 || avgAttributes.get(0).equals(DEFAULT_ATTRIBUTES)) {
- // Some volume groups may not have valid attributes, used for internal
- // volume management like patch/rerouting
- // so bailing out strategy retrieval from attributes
- continue;
- }
- AudioAttributes aa = avgAttributes.get(0);
-
- int index = mAudioManager.getVolumeIndexForAttributes(aa);
- int indexMax = mAudioManager.getMaxVolumeIndexForAttributes(aa);
- int indexMin = mAudioManager.getMinVolumeIndexForAttributes(aa);
-
- final int indexForAa = incrementVolumeIndex(index, indexMin, indexMax);
-
- // Set the receiver to filter only the current group callback
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- cb.setExpectedVolumeGroup(volumeGroupId);
- }
- mAudioManager.setVolumeIndexForAttributes(aa, indexForAa, 0/*flags*/);
-
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- assertTrue(cb.waitForExpectedVolumeGroupChanged(
- AudioVolumeGroupCallbackHelper.ASYNC_TIMEOUT_MS));
- }
- int readIndex = mAudioManager.getVolumeIndexForAttributes(aa);
- assertEquals(readIndex, indexForAa);
- }
- } finally {
- for (final AudioVolumeGroupCallbackHelper cb : validCbs) {
- audioAudioVolumeGroupChangedHandler.unregisterListener(cb);
- }
- }
- }
-}
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 94db2c02eb28..48621e4e2094 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -16,7 +16,6 @@ android_test {
name: "MediaProjectionTests",
srcs: ["**/*.java"],
-
libs: [
"android.test.base.stubs.system",
"android.test.mock.stubs.system",
@@ -30,6 +29,7 @@ android_test {
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "cts-mediaprojection-common",
"testng",
"testables",
"truth",
@@ -42,7 +42,11 @@ android_test {
"libstaticjvmtiagent",
],
- test_suites: ["device-tests"],
+ data: [
+ ":CtsMediaProjectionTestCasesHelperApp",
+ ],
+
+ test_suites: ["general-tests"],
platform_apis: true,
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
index 0c9760400ce0..514fb5f689c9 100644
--- a/media/tests/projection/AndroidManifest.xml
+++ b/media/tests/projection/AndroidManifest.xml
@@ -20,6 +20,8 @@
android:sharedUserId="com.android.uid.test">
<uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
<uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<application android:debuggable="true"
android:testOnly="true">
diff --git a/media/tests/projection/AndroidTest.xml b/media/tests/projection/AndroidTest.xml
index f64930a0eb3f..99b42d1cd263 100644
--- a/media/tests/projection/AndroidTest.xml
+++ b/media/tests/projection/AndroidTest.xml
@@ -22,6 +22,15 @@
<option name="install-arg" value="-t" />
<option name="test-file-name" value="MediaProjectionTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="force-install-mode" value="FULL"/>ss
+ <option name="test-file-name" value="CtsMediaProjectionTestCasesHelperApp.apk" />
+ </target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+ <option name="run-command" value="wm dismiss-keyguard" />
+ </target_preparer>
<option name="test-tag" value="MediaProjectionTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
diff --git a/media/tests/projection/TEST_MAPPING b/media/tests/projection/TEST_MAPPING
new file mode 100644
index 000000000000..b33097c50002
--- /dev/null
+++ b/media/tests/projection/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "MediaProjectionTests"
+ }
+ ]
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java
new file mode 100644
index 000000000000..0b84e01c4d02
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionStoppingTest.java
@@ -0,0 +1,293 @@
+/*
+ * 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 android.media.projection;
+
+import static com.android.compatibility.common.util.FeatureUtil.isAutomotive;
+import static com.android.compatibility.common.util.FeatureUtil.isTV;
+import static com.android.compatibility.common.util.FeatureUtil.isWatch;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.cts.MediaProjectionRule;
+import android.os.UserHandle;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.FrameworkSpecificTest;
+import com.android.media.projection.flags.Flags;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test {@link MediaProjection} stopping behavior.
+ *
+ * Run with:
+ * atest MediaProjectionTests:MediaProjectionStoppingTest
+ */
+@FrameworkSpecificTest
+public class MediaProjectionStoppingTest {
+ private static final String TAG = "MediaProjectionStoppingTest";
+ private static final int STOP_DIALOG_WAIT_TIMEOUT_MS = 5000;
+ private static final String CALL_HELPER_START_CALL = "start_call";
+ private static final String CALL_HELPER_STOP_CALL = "stop_call";
+ private static final String STOP_DIALOG_TITLE_RES_ID = "android:id/alertTitle";
+ private static final String STOP_DIALOG_CLOSE_BUTTON_RES_ID = "android:id/button2";
+
+ @Rule public MediaProjectionRule mMediaProjectionRule = new MediaProjectionRule();
+
+ private Context mContext;
+ private int mTimeoutMs;
+ private TelecomManager mTelecomManager;
+ private TelephonyManager mTelephonyManager;
+ private TestCallStateListener mTestCallStateListener;
+
+ @Before
+ public void setUp() throws InterruptedException {
+ mContext = InstrumentationRegistry.getInstrumentation().getContext();
+ runWithShellPermissionIdentity(
+ () -> {
+ mContext.getPackageManager()
+ .revokeRuntimePermission(
+ mContext.getPackageName(),
+ Manifest.permission.SYSTEM_ALERT_WINDOW,
+ new UserHandle(mContext.getUserId()));
+ });
+ mTimeoutMs = 1000;
+
+ mTestCallStateListener = new TestCallStateListener(mContext);
+ }
+
+ @After
+ public void cleanup() {
+ mTestCallStateListener.release();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ @ApiTest(apis = "android.media.projection.MediaProjection.Callback#onStop")
+ public void testMediaProjectionStop_callStartedAfterMediaProjection_doesNotStop()
+ throws Exception {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+
+ mMediaProjectionRule.startMediaProjection();
+
+ CountDownLatch latch = new CountDownLatch(1);
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ latch.countDown();
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ try {
+ startPhoneCall();
+ } finally {
+ endPhoneCall();
+ }
+
+ assertWithMessage("MediaProjection should not be stopped on call end")
+ .that(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS)).isFalse();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END)
+ @RequiresFlagsDisabled(Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END)
+ @ApiTest(apis = "android.media.projection.MediaProjection.Callback#onStop")
+ public void
+ testMediaProjectionStop_callStartedBeforeMediaProjection_stopDialogFlagDisabled__shouldStop()
+ throws Exception {
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+ CountDownLatch latch = new CountDownLatch(1);
+ try {
+ startPhoneCall();
+
+ mMediaProjectionRule.startMediaProjection();
+
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ latch.countDown();
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ } finally {
+ endPhoneCall();
+ }
+
+ assertWithMessage("MediaProjection was not stopped after call end")
+ .that(latch.await(mTimeoutMs, TimeUnit.MILLISECONDS)).isTrue();
+ }
+
+ @Test
+ @RequiresFlagsEnabled({
+ Flags.FLAG_STOP_MEDIA_PROJECTION_ON_CALL_END,
+ Flags.FLAG_SHOW_STOP_DIALOG_POST_CALL_END
+ })
+ public void
+ callEnds_mediaProjectionStartedDuringCallAndIsActive_stopDialogFlagEnabled_showsStopDialog()
+ throws Exception {
+ // MediaProjection stop Dialog is only available on phones.
+ assumeFalse(isWatch());
+ assumeFalse(isAutomotive());
+ assumeFalse(isTV());
+
+ assumeTrue(mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELECOM));
+
+ try {
+ startPhoneCall();
+ mMediaProjectionRule.startMediaProjection();
+
+ mMediaProjectionRule.registerCallback(
+ new MediaProjection.Callback() {
+ @Override
+ public void onStop() {
+ fail(
+ "MediaProjection should not be stopped when"
+ + " FLAG_SHOW_STOP_DIALOG_POST_CALL_END is enabled");
+ }
+ });
+ mMediaProjectionRule.createVirtualDisplay();
+
+ } finally {
+ endPhoneCall();
+ }
+
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ boolean isDialogShown =
+ device.wait(
+ Until.hasObject(By.res(STOP_DIALOG_TITLE_RES_ID)),
+ STOP_DIALOG_WAIT_TIMEOUT_MS);
+ assertWithMessage("Stop dialog should be visible").that(isDialogShown).isTrue();
+
+ // Find and click the "Close" button
+ boolean hasCloseButton =
+ device.wait(
+ Until.hasObject(By.res(STOP_DIALOG_CLOSE_BUTTON_RES_ID)),
+ STOP_DIALOG_WAIT_TIMEOUT_MS);
+ if (hasCloseButton) {
+ device.findObject(By.res(STOP_DIALOG_CLOSE_BUTTON_RES_ID)).click();
+ Log.d(TAG, "Clicked on 'Close' button to dismiss the stop dialog.");
+ } else {
+ fail("Close button not found, unable to dismiss stop dialog.");
+ }
+ }
+
+ private void startPhoneCall() throws InterruptedException {
+ mTestCallStateListener.assertCallState(false);
+ mContext.startActivity(getCallHelperIntent(CALL_HELPER_START_CALL));
+ mTestCallStateListener.waitForNextCallState(true, mTimeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ private void endPhoneCall() throws InterruptedException {
+ mTestCallStateListener.assertCallState(true);
+ mContext.startActivity(getCallHelperIntent(CALL_HELPER_STOP_CALL));
+ mTestCallStateListener.waitForNextCallState(false, mTimeoutMs, TimeUnit.MILLISECONDS);
+ }
+
+ private Intent getCallHelperIntent(String action) {
+ return new Intent(action)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ .setComponent(
+ new ComponentName(
+ "android.media.projection.cts.helper",
+ "android.media.projection.cts.helper.CallHelperActivity"));
+ }
+
+ private static final class TestCallStateListener extends TelephonyCallback
+ implements TelephonyCallback.CallStateListener {
+ private final BlockingQueue<Boolean> mCallStates = new LinkedBlockingQueue<>();
+ private final TelecomManager mTelecomManager;
+ private final TelephonyManager mTelephonyManager;
+
+ private TestCallStateListener(Context context) throws InterruptedException {
+ mTelecomManager = context.getSystemService(TelecomManager.class);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallStates.offer(isInCall());
+
+ assertThat(mCallStates.take()).isFalse();
+
+ runWithShellPermissionIdentity(
+ () ->
+ mTelephonyManager.registerTelephonyCallback(
+ context.getMainExecutor(), this));
+ }
+
+ public void release() {
+ runWithShellPermissionIdentity(
+ () -> mTelephonyManager.unregisterTelephonyCallback(this));
+ }
+
+ @Override
+ public void onCallStateChanged(int state) {
+ mCallStates.offer(isInCall());
+ }
+
+ public void waitForNextCallState(boolean expectedCallState, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ String message =
+ String.format(
+ "Call was not %s after timeout",
+ expectedCallState ? "started" : "ended");
+
+ boolean value;
+ do {
+ value = mCallStates.poll(timeout, unit);
+ } while (value != expectedCallState);
+ assertWithMessage(message).that(value).isEqualTo(expectedCallState);
+ }
+
+ private boolean isInCall() {
+ return runWithShellPermissionIdentity(mTelecomManager::isInCall);
+ }
+
+ public void assertCallState(boolean expected) {
+ assertWithMessage("Unexpected call state").that(isInCall()).isEqualTo(expected);
+ }
+ }
+}
diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml
index b480ac30d2cb..c91bf13bf98e 100644
--- a/packages/CredentialManager/wear/AndroidManifest.xml
+++ b/packages/CredentialManager/wear/AndroidManifest.xml
@@ -32,7 +32,8 @@
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:label="@string/app_name"
- android:supportsRtl="true">
+ android:supportsRtl="true"
+ android:theme="@style/Theme.CredentialSelector">
<!-- Activity called by GMS has to be exactly:
com.android.credentialmanager.CredentialSelectorActivity -->
@@ -42,7 +43,8 @@
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTop"
- android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR" />
+ android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+ android:theme="@style/Theme.CredentialSelector"/>
</application>
</manifest>
diff --git a/packages/CredentialManager/wear/res/values/themes.xml b/packages/CredentialManager/wear/res/values/themes.xml
new file mode 100644
index 000000000000..22329e9ff2ce
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index bcc737a351a9..d05aaaa6e389 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -52,8 +52,11 @@ import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Arrays;
import java.util.Objects;
+import java.util.stream.Stream;
/**
* This is a utility class for defining some utility methods and constants
@@ -69,7 +72,8 @@ public class PackageUtil {
//intent attribute strings related to uninstall
public static final String INTENT_ATTR_PACKAGE_NAME=PREFIX+"PackageName";
private static final String DOWNLOADS_AUTHORITY = "downloads";
- private static final String SPLIT_BASE_APK_END_WITH = "base.apk";
+ private static final String SPLIT_BASE_APK_SUFFIX = "base.apk";
+ private static final String SPLIT_APK_SUFFIX = ".apk";
/**
* Utility method to get package information for a given {@link File}
@@ -77,11 +81,20 @@ public class PackageUtil {
@Nullable
public static PackageInfo getPackageInfo(Context context, File sourceFile, int flags) {
String filePath = sourceFile.getAbsolutePath();
- if (filePath.endsWith(SPLIT_BASE_APK_END_WITH)) {
+ if (filePath.endsWith(SPLIT_BASE_APK_SUFFIX)) {
File dir = sourceFile.getParentFile();
- if (dir.listFiles().length > 1) {
- // split apks, use file directory to get archive info
- filePath = dir.getPath();
+ try (Stream<Path> list = Files.list(dir.toPath())) {
+ long count = list
+ .filter((name) -> name.endsWith(SPLIT_APK_SUFFIX))
+ .limit(2)
+ .count();
+ if (count > 1) {
+ // split apks, use file directory to get archive info
+ filePath = dir.getPath();
+ }
+ } catch (Exception ignored) {
+ // No access to the parent directory, proceed to read app snippet
+ // from the base apk only
}
}
try {
@@ -240,9 +253,10 @@ public class PackageUtil {
appInfo.publicSourceDir = archiveFilePath;
if (appInfo.splitNames != null && appInfo.splitSourceDirs == null) {
- final File[] files = sourceFile.getParentFile().listFiles();
+ final File[] files = sourceFile.getParentFile().listFiles(
+ (dir, name) -> name.endsWith(SPLIT_APK_SUFFIX));
final String[] splits = Arrays.stream(appInfo.splitNames)
- .map(i -> findFilePath(files, i + ".apk"))
+ .map(i -> findFilePath(files, i + SPLIT_APK_SUFFIX))
.filter(Objects::nonNull)
.toArray(String[]::new);
@@ -283,7 +297,9 @@ public class PackageUtil {
}
private static String findFilePath(File[] files, String postfix) {
- for (File file : files) {
+ final int length = files != null ? files.length : 0;
+ for (int i = 0; i < length; i++) {
+ File file = files[i];
final String path = file.getAbsolutePath();
if (path.endsWith(postfix)) {
return path;
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
index e8477ef261a8..b84b903ac1cb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/PackageUtil.kt
@@ -41,6 +41,8 @@ import android.util.Log
import com.android.packageinstaller.v2.model.PackageUtil.getAppSnippet
import java.io.ByteArrayOutputStream
import java.io.File
+import java.nio.file.Files
+import java.nio.file.Path
import kotlinx.parcelize.Parceler
import kotlinx.parcelize.Parcelize
@@ -48,6 +50,7 @@ object PackageUtil {
private val LOG_TAG = InstallRepository::class.java.simpleName
private const val DOWNLOADS_AUTHORITY = "downloads"
private const val SPLIT_BASE_APK_SUFFIX = "base.apk"
+ private const val SPLIT_APK_SUFFIX = ".apk"
const val localLogv = false
const val ARGS_ABORT_REASON: String = "abort_reason"
@@ -440,9 +443,20 @@ object PackageUtil {
var filePath = sourceFile.absolutePath
if (filePath.endsWith(SPLIT_BASE_APK_SUFFIX)) {
val dir = sourceFile.parentFile
- if ((dir?.listFiles()?.size ?: 0) > 1) {
- // split apks, use file directory to get archive info
- filePath = dir.path
+ try {
+ Files.list(dir.toPath()).use { list ->
+ val count: Long = list
+ .filter { name: Path -> name.endsWith(SPLIT_APK_SUFFIX) }
+ .limit(2)
+ .count()
+ if (count > 1) {
+ // split apks, use file directory to get archive info
+ filePath = dir.path
+ }
+ }
+ } catch (ignored: Exception) {
+ // No access to the parent directory, proceed to read app snippet
+ // from the base apk only
}
}
return try {
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 67b6fb5f2ed9..c9409e44c8ee 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -18,8 +18,9 @@ android_library {
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
"SettingsLibTile",
+ "androidx.annotation_annotation",
+ "androidx.core_core",
],
min_sdk_version: "21",
diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java
index 4d7610cf97b6..aa1d35afab4a 100644
--- a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java
+++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveIconShapeDrawable.java
@@ -23,7 +23,8 @@ import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.PathShape;
import android.util.AttributeSet;
-import android.util.PathParser;
+
+import androidx.core.graphics.PathParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
diff --git a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
index 12c234ecd739..c7ca18a9c339 100644
--- a/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
+++ b/packages/SettingsLib/AdaptiveIcon/src/com/android/settingslib/widget/AdaptiveOutlineDrawable.java
@@ -27,12 +27,12 @@ import android.graphics.Rect;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.DrawableWrapper;
import android.os.RemoteException;
-import android.util.PathParser;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
+import androidx.core.graphics.PathParser;
import com.android.settingslib.widget.adaptiveicon.R;
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java
index 8aee576c3d04..a06d8829e331 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java
@@ -24,11 +24,11 @@ import android.provider.Settings;
* Implementation of {@link SecureSettings} that uses Android's {@link Settings.Secure}
* implementation.
*/
-class AndroidSecureSettings implements SecureSettings {
+public class AndroidSecureSettings implements SecureSettings {
private final ContentResolver mContentResolver;
- AndroidSecureSettings(ContentResolver contentResolver) {
+ public AndroidSecureSettings(ContentResolver contentResolver) {
mContentResolver = contentResolver;
}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt
index fdde3d3f5669..1da17756fae6 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManager.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.devicestate
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
+import android.util.Dumpable
/**
* Interface for managing [DEVICE_STATE_ROTATION_LOCK] setting.
@@ -25,7 +26,7 @@ import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
* specific device states, retrieve the setting value, and check if rotation is locked for specific
* or all device states.
*/
-interface DeviceStateAutoRotateSettingManager {
+interface DeviceStateAutoRotateSettingManager : Dumpable {
// TODO: b/397928958 - Rename all terms from rotationLock to autoRotate in all apis.
/** Listener for changes in device-state based auto rotate setting. */
@@ -65,5 +66,3 @@ data class SettableDeviceState(
/** Returns whether there is an auto-rotation setting for this device state. */
val isSettable: Boolean
)
-
-
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt
index 0b6c6e238956..a9f9eda07118 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerImpl.kt
@@ -22,11 +22,13 @@ import android.os.UserHandle
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED
import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED
+import android.util.IndentingPrintWriter
import android.util.Log
import android.util.SparseIntArray
import com.android.internal.R
import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener
import com.android.window.flags.Flags
+import java.io.PrintWriter
import java.util.concurrent.Executor
/**
@@ -104,6 +106,15 @@ class DeviceStateAutoRotateSettingManagerImpl(
throw UnsupportedOperationException("API updateSetting is not implemented yet")
}
+ override fun dump(writer: PrintWriter, args: Array<out String>?) {
+ val indentingWriter = IndentingPrintWriter(writer)
+ indentingWriter.println("DeviceStateAutoRotateSettingManagerImpl")
+ indentingWriter.increaseIndent()
+ indentingWriter.println("fallbackPostureMap: $fallbackPostureMap")
+ indentingWriter.println("settableDeviceState: $settableDeviceState")
+ indentingWriter.decreaseIndent()
+ }
+
private fun notifyListeners() =
settingListeners.forEach { listener -> listener.onSettingsChanged() }
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerProvider.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerProvider.kt
new file mode 100644
index 000000000000..2db8e6f97498
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateAutoRotateSettingManagerProvider.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.settingslib.devicestate
+
+import android.content.Context
+import android.os.Handler
+import com.android.window.flags.Flags
+import java.util.concurrent.Executor
+
+/**
+ * Provides appropriate instance of [DeviceStateAutoRotateSettingManager], based on the value of
+ * [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR].
+ */
+object DeviceStateAutoRotateSettingManagerProvider {
+ /**
+ * Provides an instance of [DeviceStateAutoRotateSettingManager], based on the value of
+ * [Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR]. It is supposed to be used
+ * by apps that supports dagger.
+ */
+ @JvmStatic
+ fun createInstance(
+ context: Context,
+ backgroundExecutor: Executor,
+ secureSettings: SecureSettings,
+ mainHandler: Handler,
+ posturesHelper: PosturesHelper,
+ ): DeviceStateAutoRotateSettingManager =
+ if (Flags.enableDeviceStateAutoRotateSettingRefactor()) {
+ DeviceStateAutoRotateSettingManagerImpl(
+ context,
+ backgroundExecutor,
+ secureSettings,
+ mainHandler,
+ posturesHelper,
+ )
+ } else {
+ DeviceStateRotationLockSettingsManager(context, secureSettings)
+ }
+}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index deeba574f2ad..6d180b63cd08 100644
--- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
@@ -20,10 +20,8 @@ import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORE
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_LOCKED;
import static android.provider.Settings.Secure.DEVICE_STATE_ROTATION_LOCK_UNLOCKED;
-import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager.DeviceStateAutoRotateSettingListener;
-
+import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -41,6 +39,7 @@ import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -51,7 +50,8 @@ import java.util.Set;
* Manages device-state based rotation lock settings. Handles reading, writing, and listening for
* changes.
*/
-public final class DeviceStateRotationLockSettingsManager {
+public final class DeviceStateRotationLockSettingsManager implements
+ DeviceStateAutoRotateSettingManager {
private static final String TAG = "DSRotLockSettingsMngr";
private static final String SEPARATOR_REGEX = ":";
@@ -68,8 +68,7 @@ public final class DeviceStateRotationLockSettingsManager {
private SparseIntArray mPostureRotationLockFallbackSettings;
private List<SettableDeviceState> mSettableDeviceStates;
- @VisibleForTesting
- DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
+ public DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) {
mSecureSettings = secureSettings;
mPosturesHelper = new PosturesHelper(context, getDeviceStateManager(context));
@@ -89,30 +88,6 @@ public final class DeviceStateRotationLockSettingsManager {
return null;
}
- /** Returns a singleton instance of this class */
- public static synchronized DeviceStateRotationLockSettingsManager getInstance(Context context) {
- if (sSingleton == null) {
- Context applicationContext = context.getApplicationContext();
- ContentResolver contentResolver = applicationContext.getContentResolver();
- SecureSettings secureSettings = new AndroidSecureSettings(contentResolver);
- sSingleton =
- new DeviceStateRotationLockSettingsManager(applicationContext, secureSettings);
- }
- return sSingleton;
- }
-
- /** Resets the singleton instance of this class. Only used for testing. */
- @VisibleForTesting
- public static synchronized void resetInstance() {
- sSingleton = null;
- }
-
- /** Returns true if device-state based rotation lock settings are enabled. */
- public static boolean isDeviceStateRotationLockEnabled(Context context) {
- return context.getResources()
- .getStringArray(R.array.config_perDeviceStateRotationLockDefaults).length > 0;
- }
-
private void listenForSettingsChange() {
mSecureSettings
.registerContentObserver(
@@ -131,7 +106,8 @@ public final class DeviceStateRotationLockSettingsManager {
* Registers a {@link DeviceStateAutoRotateSettingListener} to be notified when the settings
* change. Can be called multiple times with different listeners.
*/
- public void registerListener(DeviceStateAutoRotateSettingListener runnable) {
+ @Override
+ public void registerListener(@NonNull DeviceStateAutoRotateSettingListener runnable) {
mListeners.add(runnable);
}
@@ -139,14 +115,16 @@ public final class DeviceStateRotationLockSettingsManager {
* Unregisters a {@link DeviceStateAutoRotateSettingListener}. No-op if the given instance
* was never registered.
*/
+ @Override
public void unregisterListener(
- DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) {
+ @NonNull DeviceStateAutoRotateSettingListener deviceStateAutoRotateSettingListener) {
if (!mListeners.remove(deviceStateAutoRotateSettingListener)) {
Log.w(TAG, "Attempting to unregister a listener hadn't been registered");
}
}
/** Updates the rotation lock setting for a specified device state. */
+ @Override
public void updateSetting(int deviceState, boolean rotationLocked) {
int posture = mPosturesHelper.deviceStateToPosture(deviceState);
if (mPostureRotationLockFallbackSettings.indexOfKey(posture) >= 0) {
@@ -173,6 +151,7 @@ public final class DeviceStateRotationLockSettingsManager {
* DEVICE_STATE_ROTATION_LOCK_IGNORED}.
*/
@Settings.Secure.DeviceStateRotationLockSetting
+ @Override
public int getRotationLockSetting(int deviceState) {
int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState);
int rotationLockSetting = mPostureRotationLockSettings.get(
@@ -196,6 +175,7 @@ public final class DeviceStateRotationLockSettingsManager {
/** Returns true if the rotation is locked for the current device state */
+ @Override
public boolean isRotationLocked(int deviceState) {
return getRotationLockSetting(deviceState) == DEVICE_STATE_ROTATION_LOCK_LOCKED;
}
@@ -204,6 +184,7 @@ public final class DeviceStateRotationLockSettingsManager {
* Returns true if there is no device state for which the current setting is {@link
* DEVICE_STATE_ROTATION_LOCK_UNLOCKED}.
*/
+ @Override
public boolean isRotationLockedForAllStates() {
for (int i = 0; i < mPostureRotationLockSettings.size(); i++) {
if (mPostureRotationLockSettings.valueAt(i)
@@ -215,6 +196,8 @@ public final class DeviceStateRotationLockSettingsManager {
}
/** Returns a list of device states and their respective auto-rotation setting availability. */
+ @Override
+ @NonNull
public List<SettableDeviceState> getSettableDeviceStates() {
// Returning a copy to make sure that nothing outside can mutate our internal list.
return new ArrayList<>(mSettableDeviceStates);
@@ -356,17 +339,21 @@ public final class DeviceStateRotationLockSettingsManager {
}
}
- /** Dumps internal state. */
- public void dump(IndentingPrintWriter pw) {
- pw.println("DeviceStateRotationLockSettingsManager");
- pw.increaseIndent();
- pw.println("mPostureRotationLockDefaults: "
+ @Override
+ public void dump(@NonNull PrintWriter writer, String[] args) {
+ IndentingPrintWriter indentingWriter = new IndentingPrintWriter(writer);
+ indentingWriter.println("DeviceStateRotationLockSettingsManager");
+ indentingWriter.increaseIndent();
+ indentingWriter.println("mPostureRotationLockDefaults: "
+ Arrays.toString(mPostureRotationLockDefaults));
- pw.println("mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings);
- pw.println("mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings);
- pw.println("mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings);
- pw.println("mSettableDeviceStates: " + mSettableDeviceStates);
- pw.decreaseIndent();
+ indentingWriter.println(
+ "mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings);
+ indentingWriter.println(
+ "mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings);
+ indentingWriter.println(
+ "mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings);
+ indentingWriter.println("mSettableDeviceStates: " + mSettableDeviceStates);
+ indentingWriter.decreaseIndent();
}
/**
diff --git a/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
index 7d7bec14ed78..cc55cacb9e5c 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout/settingslib_expressive_collapsable_textview.xml
@@ -44,10 +44,11 @@
<com.android.settingslib.widget.LinkableTextView
android:id="@+id/settingslib_expressive_learn_more"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@android:id/title"
app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
android:textAlignment="viewStart"
android:clickable="true"
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
index 976711bdc5f3..007dc5143262 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/CollapsableTextView.kt
@@ -85,6 +85,7 @@ class CollapsableTextView @JvmOverloads constructor(
Gravity.CENTER_VERTICAL, Gravity.CENTER, Gravity.CENTER_HORIZONTAL -> {
centerHorizontally(titleTextView)
centerHorizontally(collapseButton)
+ centerHorizontally(learnMoreTextView)
}
}
isCollapsable = getBoolean(isCollapsableAttr, DEFAULT_COLLAPSABLE)
diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
index 2672787a0519..d1c88de3f399 100644
--- a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
+++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt
@@ -71,18 +71,27 @@ open class SettingsPreferenceGroupAdapter(preferenceGroup: PreferenceGroup) :
override fun onPreferenceHierarchyChange(preference: Preference) {
super.onPreferenceHierarchyChange(preference)
- // Post after super class has posted their sync runnable to update preferences.
- mHandler.removeCallbacks(syncRunnable)
- mHandler.post(syncRunnable)
+ if (SettingsThemeHelper.isExpressiveTheme(preference.context)) {
+ // Post after super class has posted their sync runnable to update preferences.
+ mHandler.removeCallbacks(syncRunnable)
+ mHandler.post(syncRunnable)
+ }
}
@SuppressLint("RestrictedApi")
override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) {
super.onBindViewHolder(holder, position)
- updateBackground(holder, position)
+
+ if (SettingsThemeHelper.isExpressiveTheme(holder.itemView.context)) {
+ updateBackground(holder, position)
+ }
}
private fun updatePreferencesList() {
+ if (!SettingsThemeHelper.isExpressiveTheme(mPreferenceGroup.context)) {
+ return
+ }
+
val oldList = ArrayList(mRoundCornerMappingList)
mRoundCornerMappingList = ArrayList()
mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup)
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 50408190b3ef..25406d794af9 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -28,7 +28,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.8.0-beta02"
+ extra["jetpackComposeVersion"] = "1.8.0-rc01"
}
subprojects {
@@ -36,11 +36,11 @@ subprojects {
plugins.withType<AndroidBasePlugin> {
configure<BaseExtension> {
- compileSdkVersion(35)
+ compileSdkVersion(36)
defaultConfig {
minSdk = 21
- targetSdk = 35
+ targetSdk = 36
}
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 8636524ed23c..b9a3a7f4b48f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -124,4 +124,6 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
// For debugging
override val searchProviderAuthorities = "com.android.spa.gallery.search.provider"
+
+ override val isSpaExpressiveEnabled = true
}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index b074f4b91ed0..d041eb011986 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,10 +15,10 @@
#
[versions]
-agp = "8.8.1"
+agp = "8.9.0"
dexmaker-mockito = "2.28.3"
jvm = "21"
-kotlin = "2.0.21"
+kotlin = "2.1.10"
truth = "1.4.4"
[libraries]
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 7ce5b71f678e..57f5520fd659 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -52,18 +52,18 @@ android {
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.4.0-alpha08")
- api("androidx.compose.material:material-icons-extended")
+ api("androidx.compose.material3:material3:1.4.0-alpha10")
+ api("androidx.compose.material:material-icons-extended:1.7.8")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.graphics:graphics-shapes-android:1.0.1")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.9.0-alpha06")
+ api("androidx.navigation:navigation-compose:2.9.0-alpha08")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.13.0-alpha08")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
- implementation("com.airbnb.android:lottie-compose:6.4.0")
+ implementation("com.airbnb.android:lottie-compose:6.5.2")
androidTestImplementation(project(":testutils"))
androidTestImplementation(libs.dexmaker.mockito)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
index f10f96afd389..395328f86047 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt
@@ -78,6 +78,8 @@ fun CategoryTitle(title: String) {
/**
* A container that is used to group similar items. A [Category] displays a [CategoryTitle] and
* visually separates groups of items.
+ *
+ * @param content The content of the category.
*/
@Composable
fun Category(
@@ -126,7 +128,8 @@ fun Category(
* be decided by the index.
* @param bottomPadding Optional. Bottom outside padding of the category.
* @param state Optional. State of LazyList.
- * @param content Optional. Content to be shown at the top of the category.
+ * @param footer Optional. Content to be shown at the bottom of the category.
+ * @param header Optional. Content to be shown at the top of the category.
*/
@Composable
fun LazyCategory(
@@ -136,7 +139,8 @@ fun LazyCategory(
title: ((Int) -> String?)? = null,
bottomPadding: Dp = SettingsDimension.paddingSmall,
state: LazyListState = rememberLazyListState(),
- content: @Composable () -> Unit,
+ footer: @Composable () -> Unit = {},
+ header: @Composable () -> Unit,
) {
Column(
Modifier.padding(
@@ -154,12 +158,14 @@ fun LazyCategory(
verticalArrangement = Arrangement.spacedBy(SettingsDimension.paddingTiny),
state = state,
) {
- item { CompositionLocalProvider(LocalIsInCategory provides true) { content() } }
+ item { CompositionLocalProvider(LocalIsInCategory provides true) { header() } }
items(count = list.size, key = key) {
title?.invoke(it)?.let { title -> CategoryTitle(title) }
CompositionLocalProvider(LocalIsInCategory provides true) { entry(it)() }
}
+
+ item { CompositionLocalProvider(LocalIsInCategory provides true) { footer() } }
}
}
}
@@ -189,3 +195,28 @@ private fun CategoryPreview() {
}
}
}
+
+@Preview
+@Composable
+private fun LazyCategoryPreview() {
+ SettingsTheme {
+ LazyCategory(
+ list = listOf(1, 2, 3),
+ entry = { key ->
+ @Composable {
+ Preference(
+ object : PreferenceModel {
+ override val title = key.toString()
+ }
+ )
+ }
+ },
+ footer = @Composable {
+ Footer("Footer")
+ },
+ header = @Composable {
+ Text("Header")
+ },
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
index 4b4a8c20b39e..7d199511044a 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CategoryTest.kt
@@ -71,10 +71,17 @@ class CategoryTest {
}
@Test
- fun lazyCategory_content_displayed() {
+ fun lazyCategory_headerDisplayed() {
composeTestRule.setContent { TestLazyCategory() }
- composeTestRule.onNodeWithText("text").assertExists()
+ composeTestRule.onNodeWithText("Header").assertExists()
+ }
+
+ @Test
+ fun lazyCategory_footerDisplayed() {
+ composeTestRule.setContent { TestLazyCategory() }
+
+ composeTestRule.onNodeWithText("Footer").assertExists()
}
@Test
@@ -102,8 +109,8 @@ private fun TestLazyCategory() {
list = list,
entry = { index: Int -> @Composable { Preference(list[index]) } },
title = { index: Int -> if (index == 0) "LazyCategory $index" else null },
- ) {
- Text("text")
- }
+ footer = @Composable { Footer("Footer") },
+ header = @Composable { Text("Header") },
+ )
}
}
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 349d13a29b05..90e5a010416c 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -37,16 +37,6 @@ flag {
}
flag {
- name: "enable_set_preferred_transport_for_le_audio_device"
- namespace: "bluetooth"
- description: "Enable setting preferred transport for Le Audio device"
- bug: "330581926"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "enable_determining_advanced_details_header_with_metadata"
namespace: "pixel_cross_device_control"
description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header."
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
index e7ddc46093e3..f7da8a57410f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/AmbientVolumeUiController.java
@@ -80,6 +80,7 @@ public class AmbientVolumeUiController implements
mLocalDataManager = new HearingDeviceLocalDataManager(context);
mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
ThreadUtils.getBackgroundExecutor());
+ mLocalDataManager.start();
}
@VisibleForTesting
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 914d96266e43..edec2e427315 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -75,6 +75,7 @@ import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
/**
@@ -154,8 +155,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
private boolean mIsLeAudioProfileConnectedFail = false;
private boolean mUnpairing;
@Nullable
- private final InputDevice mInputDevice;
- private final boolean mIsDeviceStylus;
+ private InputDevice mInputDevice;
+ private boolean mIsDeviceStylus;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
@@ -313,8 +314,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = true;
}
}
- if (Flags.enableSetPreferredTransportForLeAudioDevice()
- && profile instanceof HidProfile) {
+ if (profile instanceof HidProfile) {
updatePreferredTransport();
}
} else if (profile instanceof MapProfile
@@ -329,8 +329,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mLocalNapRoleConnected = false;
}
- if (Flags.enableSetPreferredTransportForLeAudioDevice()
- && profile instanceof LeAudioProfile) {
+ if (profile instanceof LeAudioProfile) {
updatePreferredTransport();
}
@@ -762,11 +761,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
* {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
*/
public int getMinBatteryLevelWithMemberDevices() {
- return Stream.concat(Stream.of(this), mMemberDevices.stream())
- .mapToInt(cachedDevice -> cachedDevice.getBatteryLevel())
- .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
- .min()
- .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ return getMinBatteryLevels(Stream.concat(Stream.of(this), mMemberDevices.stream())
+ .mapToInt(CachedBluetoothDevice::getBatteryLevel));
}
/**
@@ -789,6 +785,13 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
: null;
}
+ private int getMinBatteryLevels(IntStream batteryLevels) {
+ return batteryLevels
+ .filter(battery -> battery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .min()
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ }
+
void refresh() {
ListenableFuture<Void> future = ThreadUtils.getBackgroundExecutor().submit(() -> {
if (BluetoothUtils.isAdvancedDetailsHeader(mDevice)) {
@@ -1674,10 +1677,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
return null;
} else {
int overallBattery =
- Arrays.stream(new int[]{leftBattery, rightBattery, caseBattery})
- .filter(battery -> battery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
- .min()
- .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ getMinBatteryLevels(
+ Arrays.stream(new int[]{leftBattery, rightBattery, caseBattery}));
Log.d(TAG, "Acquired battery info from metadata for untethered device "
+ mDevice.getAnonymizedAddress()
+ " left earbud battery: " + leftBattery
@@ -1711,10 +1712,75 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
@Nullable
private BatteryLevelsInfo getBatteryFromBluetoothService() {
- // TODO(b/397847825): Implement the logic to get battery from Bluetooth service.
- return null;
+ BatteryLevelsInfo batteryLevelsInfo;
+ if (isConnectedHearingAidDevice()) {
+ // If the device is hearing aid device, sides can be distinguished by HearingAidInfo.
+ batteryLevelsInfo = getBatteryOfHearingAidDeviceComponents();
+ if (batteryLevelsInfo != null) {
+ return batteryLevelsInfo;
+ }
+ }
+ if (isConnectedLeAudioDevice()) {
+ // If the device is LE Audio device, sides can be distinguished by LeAudioProfile.
+ batteryLevelsInfo = getBatteryOfLeAudioDeviceComponents();
+ if (batteryLevelsInfo != null) {
+ return batteryLevelsInfo;
+ }
+ }
+ int overallBattery = getMinBatteryLevelWithMemberDevices();
+ return overallBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ ? new BatteryLevelsInfo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ overallBattery)
+ : null;
}
+ @Nullable
+ private BatteryLevelsInfo getBatteryOfHearingAidDeviceComponents() {
+ if (getDeviceSide() == HearingAidInfo.DeviceSide.SIDE_LEFT_AND_RIGHT) {
+ return new BatteryLevelsInfo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ mDevice.getBatteryLevel());
+ }
+
+ int leftBattery = getHearingAidSideBattery(HearingAidInfo.DeviceSide.SIDE_LEFT);
+ int rightBattery = getHearingAidSideBattery(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+ int overallBattery = getMinBatteryLevels(
+ Arrays.stream(new int[]{leftBattery, rightBattery}));
+
+ Log.d(TAG, "Acquired battery info from Bluetooth service for hearing aid device "
+ + mDevice.getAnonymizedAddress()
+ + " left battery: " + leftBattery
+ + " right battery: " + rightBattery
+ + " overall battery: " + overallBattery);
+ return overallBattery > BluetoothDevice.BATTERY_LEVEL_UNKNOWN
+ ? new BatteryLevelsInfo(
+ leftBattery,
+ rightBattery,
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
+ overallBattery)
+ : null;
+ }
+
+ private int getHearingAidSideBattery(int side) {
+ Optional<CachedBluetoothDevice> connectedHearingAidSide = getConnectedHearingAidSide(side);
+ return connectedHearingAidSide.isPresent()
+ ? connectedHearingAidSide
+ .map(CachedBluetoothDevice::getBatteryLevel)
+ .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+ : BluetoothDevice.BATTERY_LEVEL_UNKNOWN;
+ }
+
+ @Nullable
+ private BatteryLevelsInfo getBatteryOfLeAudioDeviceComponents() {
+ // TODO(b/397847825): Implement the logic to get battery of LE audio device components.
+ return null;
+ }
private CharSequence getTvBatterySummary(int mainBattery, int leftBattery, int rightBattery,
int lowBatteryColorRes) {
// Since there doesn't seem to be a way to use format strings to add the
@@ -1833,10 +1899,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
// Retrieve hearing aids (ASHA, HAP) individual side battery level
if (leftBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- leftBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_LEFT)
- .map(CachedBluetoothDevice::getBatteryLevel)
- .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
- .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ leftBattery = getHearingAidSideBattery(HearingAidInfo.DeviceSide.SIDE_LEFT);
}
return leftBattery;
@@ -1852,10 +1915,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
// Retrieve hearing aids (ASHA, HAP) individual side battery level
if (rightBattery == BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
- rightBattery = getConnectedHearingAidSide(HearingAidInfo.DeviceSide.SIDE_RIGHT)
- .map(CachedBluetoothDevice::getBatteryLevel)
- .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
- .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ rightBattery = getHearingAidSideBattery(HearingAidInfo.DeviceSide.SIDE_RIGHT);
}
return rightBattery;
@@ -2263,6 +2323,16 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
mBluetoothManager = bluetoothManager;
}
+ @VisibleForTesting
+ void setIsDeviceStylus(Boolean isDeviceStylus) {
+ mIsDeviceStylus = isDeviceStylus;
+ }
+
+ @VisibleForTesting
+ void setInputDevice(@Nullable InputDevice inputDevice) {
+ mInputDevice = inputDevice;
+ }
+
private boolean isAndroidAuto() {
try {
ParcelUuid[] uuids = mDevice.getUuids();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index bf86911ee683..f2c013598cdc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -431,7 +431,10 @@ public class CsipDeviceManager {
for (BluetoothDevice device : sinksToSync) {
log("addMemberDevicesIntoMainDevice: sync audio sharing source to "
+ device.getAnonymizedAddress());
- assistant.addSource(device, metadata, /* isGroupOp= */ false);
+ if (assistant.getConnectionStatus(device)
+ == BluetoothProfile.STATE_CONNECTED) {
+ assistant.addSource(device, metadata, /* isGroupOp= */ false);
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
index c4e724554c04..21d518a644a9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/ZenModeRepository.kt
@@ -27,7 +27,6 @@ import android.content.IntentFilter
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
-import com.android.settingslib.flags.Flags
import com.android.settingslib.notification.modes.ZenMode
import com.android.settingslib.notification.modes.ZenModesBackend
import java.time.Duration
@@ -35,6 +34,7 @@ import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
@@ -72,7 +72,7 @@ class ZenModeRepositoryImpl(
private val notificationManager: NotificationManager,
private val backend: ZenModesBackend,
private val contentResolver: ContentResolver,
- val scope: CoroutineScope,
+ val applicationScope: CoroutineScope,
val backgroundCoroutineContext: CoroutineContext,
// This is nullable just to simplify testing, since SettingsLib doesn't have a good way
// to create a fake handler.
@@ -104,7 +104,7 @@ class ZenModeRepositoryImpl(
awaitClose { context.unregisterReceiver(receiver) }
}
.flowOn(backgroundCoroutineContext)
- .shareIn(started = SharingStarted.WhileSubscribed(), scope = scope)
+ .shareIn(started = SharingStarted.WhileSubscribed(), scope = applicationScope)
}
override val consolidatedNotificationPolicy: StateFlow<NotificationManager.Policy?> by lazy {
@@ -129,14 +129,11 @@ class ZenModeRepositoryImpl(
.map { mapper(it) }
.onStart { emit(mapper(null)) }
.flowOn(backgroundCoroutineContext)
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), null)
private val zenConfigChanged by lazy {
if (android.app.Flags.modesUi()) {
callbackFlow {
- // emit an initial value
- trySend(Unit)
-
val observer =
object : ContentObserver(backgroundHandler) {
override fun onChange(selfChange: Boolean) {
@@ -163,16 +160,18 @@ class ZenModeRepositoryImpl(
}
}
- override val modes: Flow<List<ZenMode>> by lazy {
- if (android.app.Flags.modesUi()) {
+ override val modes: StateFlow<List<ZenMode>> =
+ if (android.app.Flags.modesUi())
zenConfigChanged
.map { backend.modes }
.distinctUntilChanged()
.flowOn(backgroundCoroutineContext)
- } else {
- flowOf(emptyList())
- }
- }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = backend.modes,
+ )
+ else MutableStateFlow<List<ZenMode>>(emptyList())
/**
* Gets the current list of [ZenMode] instances according to the backend.
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt
index 78dba57028ba..a9329ba3c76c 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerImplTest.kt
@@ -181,8 +181,10 @@ class DeviceStateAutoRotateSettingManagerImplTest {
@Test
fun getAutoRotateSetting_forInvalidPosture_returnsSettingForFallbackPosture() {
- persistSettings(DEVICE_STATE_ROTATION_KEY_UNFOLDED, DEVICE_STATE_ROTATION_LOCK_UNLOCKED)
- persistSettings(DEVICE_STATE_ROTATION_KEY_FOLDED, DEVICE_STATE_ROTATION_LOCK_LOCKED)
+ persistSettings(
+ "$DEVICE_STATE_ROTATION_KEY_FOLDED:$DEVICE_STATE_ROTATION_LOCK_LOCKED:" +
+ "$DEVICE_STATE_ROTATION_KEY_UNFOLDED:$DEVICE_STATE_ROTATION_LOCK_UNLOCKED"
+ )
val autoRotateSetting = settingManager.getRotationLockSetting(DEVICE_STATE_HALF_FOLDED)
@@ -276,7 +278,6 @@ class DeviceStateAutoRotateSettingManagerImplTest {
SettableDeviceState(DEVICE_STATE_ROTATION_KEY_FOLDED, isSettable = true),
SettableDeviceState(DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, isSettable = false),
SettableDeviceState(DEVICE_STATE_ROTATION_KEY_REAR_DISPLAY, isSettable = false),
- SettableDeviceState(DEVICE_STATE_ROTATION_LOCK_IGNORED, isSettable = false),
)
}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerProviderTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerProviderTest.kt
new file mode 100644
index 000000000000..c3ec4edfdee5
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateAutoRotateSettingManagerProviderTest.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.settingslib.devicestate
+
+import android.content.Context
+import android.os.Handler
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceStateAutoRotateSettingManagerProviderTest {
+
+ @get:Rule
+ val setFlagsRule: SetFlagsRule = SetFlagsRule()
+
+ @get:Rule val rule = MockitoJUnit.rule()
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ @Mock
+ private lateinit var mockExecutor: Executor
+
+ @Mock
+ private lateinit var mockSecureSettings: SecureSettings
+
+ @Mock
+ private lateinit var mockMainHandler: Handler
+
+ @Mock
+ private lateinit var mockPosturesHelper: PosturesHelper
+
+ @Before
+ fun setup() {
+ whenever(mockSecureSettings.getStringForUser(any(), anyInt())).thenReturn("")
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
+ fun createInstance_refactorFlagEnabled_returnsRefactoredManager() {
+ val manager =
+ DeviceStateAutoRotateSettingManagerProvider.createInstance(
+ context, mockExecutor, mockSecureSettings, mockMainHandler, mockPosturesHelper
+ )
+
+ assertThat(manager).isInstanceOf(DeviceStateAutoRotateSettingManagerImpl::class.java)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_DEVICE_STATE_AUTO_ROTATE_SETTING_REFACTOR)
+ fun createInstance_refactorFlagDisabled_returnsLegacyManager() {
+ val manager =
+ DeviceStateAutoRotateSettingManagerProvider.createInstance(
+ context, mockExecutor, mockSecureSettings, mockMainHandler, mockPosturesHelper
+ )
+
+ assertThat(manager).isInstanceOf(DeviceStateRotationLockSettingsManager::class.java)
+ }
+}
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
index baebaf7dfef0..b23ea5f1786f 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java
@@ -151,8 +151,8 @@ public class DeviceStateRotationLockSettingsManagerTest {
new String[]{"2:1", "1:0:1", "0:2"});
List<SettableDeviceState> settableDeviceStates =
- DeviceStateRotationLockSettingsManager.getInstance(
- mMockContext).getSettableDeviceStates();
+ new DeviceStateRotationLockSettingsManager(mMockContext,
+ mFakeSecureSettings).getSettableDeviceStates();
assertThat(settableDeviceStates).containsExactly(
new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true),
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index e29adc151a28..b4384b74ccbe 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.bluetooth;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_LE_AUDIO_SHARING;
-import static com.android.settingslib.flags.Flags.FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE;
import static com.android.settingslib.flags.Flags.FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI;
import static com.google.common.truth.Truth.assertThat;
@@ -140,7 +139,6 @@ public class CachedBluetoothDeviceTest {
public void setUp() {
MockitoAnnotations.initMocks(this);
mSetFlagsRule.enableFlags(Flags.FLAG_ENABLE_TV_MEDIA_OUTPUT_DIALOG);
- mSetFlagsRule.enableFlags(FLAG_ENABLE_SET_PREFERRED_TRANSPORT_FOR_LE_AUDIO_DEVICE);
mSetFlagsRule.enableFlags(FLAG_ENABLE_LE_AUDIO_SHARING);
mSetFlagsRule.enableFlags(FLAG_ENABLE_TEMPORARY_BOND_DEVICES_UI);
mContext = RuntimeEnvironment.application;
@@ -2300,11 +2298,7 @@ public class CachedBluetoothDeviceTest {
"false".getBytes());
when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
MAIN_BATTERY.getBytes());
- when(mContext.getSystemService(InputManager.class)).thenReturn(mInputManager);
- when(mInputManager.getInputDeviceIds()).thenReturn(new int[]{TEST_DEVICE_ID});
- when(mInputManager.getInputDeviceBluetoothAddress(TEST_DEVICE_ID)).thenReturn(
- DEVICE_ADDRESS);
- when(mInputManager.getInputDevice(TEST_DEVICE_ID)).thenReturn(mInputDevice);
+ mCachedDevice.setInputDevice(mInputDevice);
BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
@@ -2322,10 +2316,9 @@ public class CachedBluetoothDeviceTest {
public void getBatteryLevelsInfo_stylusDeviceWithBattery_returnBatteryLevelsInfo() {
when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
"false".getBytes());
- when(mDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE)).thenReturn(
- BluetoothDevice.DEVICE_TYPE_STYLUS.getBytes());
when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(
MAIN_BATTERY.getBytes());
+ mCachedDevice.setIsDeviceStylus(true);
BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
@@ -2339,6 +2332,31 @@ public class CachedBluetoothDeviceTest {
Integer.parseInt(MAIN_BATTERY));
}
+ @Test
+ public void getBatteryLevelsInfo_hearingAidDeviceWithBattery_returnBatteryLevelsInfo() {
+ when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn(
+ "false".getBytes());
+ when(mProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+ updateProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mSubCachedDevice.setHearingAidInfo(getLeftAshaHearingAidInfo());
+ when(mSubCachedDevice.getBatteryLevel()).thenReturn(Integer.parseInt(TWS_BATTERY_LEFT));
+ updateSubDeviceProfileStatus(mHearingAidProfile, BluetoothProfile.STATE_CONNECTED);
+ mCachedDevice.setSubDevice(mSubCachedDevice);
+ mCachedDevice.setHearingAidInfo(getRightAshaHearingAidInfo());
+ when(mCachedDevice.getBatteryLevel()).thenReturn(Integer.parseInt(TWS_BATTERY_RIGHT));
+
+ BatteryLevelsInfo batteryLevelsInfo = mCachedDevice.getBatteryLevelsInfo();
+
+ assertThat(batteryLevelsInfo.getLeftBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_LEFT));
+ assertThat(batteryLevelsInfo.getRightBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_RIGHT));
+ assertThat(batteryLevelsInfo.getCaseBatteryLevel()).isEqualTo(
+ BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+ assertThat(batteryLevelsInfo.getOverallBatteryLevel()).isEqualTo(
+ Integer.parseInt(TWS_BATTERY_LEFT));
+ }
+
private void updateProfileStatus(LocalBluetoothProfile profile, int status) {
doReturn(status).when(profile).getConnectionStatus(mDevice);
mCachedDevice.onProfileStateChanged(profile, status);
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..4314982752c3 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
@@ -377,6 +377,7 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mAssistant.getConnectionStatus(mDevice1)).thenReturn(BluetoothAdapter.STATE_CONNECTED);
when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
when(mUserManager.isManagedProfile()).thenReturn(true);
@@ -407,6 +408,7 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice2)).thenReturn(ImmutableList.of(state));
+ when(mAssistant.getConnectionStatus(mDevice1)).thenReturn(BluetoothAdapter.STATE_CONNECTED);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
@@ -474,6 +476,8 @@ public class CsipDeviceManagerTest {
BluetoothLeBroadcastReceiveState.class);
when(state.getBisSyncState()).thenReturn(ImmutableList.of(1L));
when(mAssistant.getAllSources(mDevice1)).thenReturn(ImmutableList.of(state));
+ when(mAssistant.getConnectionStatus(mDevice2)).thenReturn(BluetoothAdapter.STATE_CONNECTED);
+ when(mAssistant.getConnectionStatus(mDevice3)).thenReturn(BluetoothAdapter.STATE_CONNECTED);
assertThat(mCsipDeviceManager.addMemberDevicesIntoMainDevice(GROUP1, preferredDevice))
.isTrue();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
index b364368df473..ec7baf6bf081 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/data/repository/ZenModeRepositoryTest.kt
@@ -75,10 +75,14 @@ class ZenModeRepositoryTest {
private val testScope: TestScope = TestScope()
+ private val initialModes = listOf(TestModeBuilder().setId("Built-in").build())
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ `when`(zenModesBackend.modes).thenReturn(initialModes)
+
underTest =
ZenModeRepositoryImpl(
context,
@@ -151,8 +155,8 @@ class ZenModeRepositoryTest {
fun modesListEmitsOnSettingsChange() {
testScope.runTest {
val values = mutableListOf<List<ZenMode>>()
- val modes1 = listOf(TestModeBuilder().setId("One").build())
- `when`(zenModesBackend.modes).thenReturn(modes1)
+
+ // an initial list of modes is read when the stateflow is created
underTest.modes.onEach { values.add(it) }.launchIn(backgroundScope)
runCurrent()
@@ -172,7 +176,7 @@ class ZenModeRepositoryTest {
triggerZenModeSettingUpdate()
runCurrent()
- assertThat(values).containsExactly(modes1, modes2, modes3).inOrder()
+ assertThat(values).containsExactly(initialModes, modes2, modes3).inOrder()
}
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d0f84627f8d8..463e94bfa0d6 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -464,6 +464,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
new InclusiveIntegerRangeValidator(0, 1));
VALIDATORS.put(Secure.ADVANCED_PROTECTION_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.AAPM_USB_DATA_PROTECTION, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index ed11e12c32ff..527a1f16a84f 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -16,6 +16,8 @@
package com.android.providers.settings;
+import static com.android.settingslib.devicestate.DeviceStateAutoRotateSettingUtils.isDeviceStateRotationLockEnabled;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -49,11 +51,11 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
import com.android.server.backup.Flags;
-import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
@@ -347,7 +349,7 @@ public class SettingsHelper {
private boolean shouldSkipAutoRotateRestore() {
// When device state based auto rotation settings are available, let's skip the restoring
// of the standard auto rotation settings to avoid conflicting setting values.
- return DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(mContext);
+ return isDeviceStateRotationLockEnabled(mContext);
}
public String onBackupValue(String name, String value) {
@@ -650,6 +652,10 @@ public class SettingsHelper {
* e.g. current locale "en-US,zh-CN" and backup locale "ja-JP,zh-Hans-CN,en-US" are merged to
* "en-US,zh-CN,ja-JP".
*
+ * - Same language codes and scripts are dropped.
+ * e.g. current locale "en-US, zh-Hans-TW" and backup locale "en-UK, en-GB, zh-Hans-HK" are
+ * merged to "en-US, zh-Hans-TW".
+ *
* - Unsupported locales are dropped.
* e.g. current locale "en-US" and backup locale "ja-JP,zh-CN" but the supported locales
* are "en-US,zh-CN", the merged locale list is "en-US,zh-CN".
@@ -683,13 +689,23 @@ public class SettingsHelper {
filtered.add(locale);
}
+ final HashSet<String> existingLanguageAndScript = new HashSet<>();
for (int i = 0; i < restore.size(); i++) {
final Locale restoredLocaleWithExtension = copyExtensionToTargetLocale(restoredLocale,
getFilteredLocale(restore.get(i), allLocales));
+
if (restoredLocaleWithExtension != null) {
- filtered.add(restoredLocaleWithExtension);
+ String language = restoredLocaleWithExtension.getLanguage();
+ String script = restoredLocaleWithExtension.getScript();
+
+ String restoredLanguageAndScript =
+ script == null ? language : language + "-" + script;
+ if (existingLanguageAndScript.add(restoredLanguageAndScript)) {
+ filtered.add(restoredLocaleWithExtension);
+ }
}
}
+
if (filtered.size() == current.size()) {
return current; // Nothing added to current locale list.
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 7179cbdf93fb..e9b6c73cb800 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -642,6 +642,7 @@ public class SettingsBackupTest {
private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
newHashSet(
+ Settings.Secure.AAPM_USB_DATA_PROTECTION,
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, // Deprecated since O.
Settings.Secure.ALLOW_PRIMARY_GAIA_ACCOUNT_REMOVAL_FOR_TESTS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index 40654b0e2f37..48c778542d66 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -388,7 +388,11 @@ public class SettingsHelperTest {
LocaleList.forLanguageTags("zh-Hant-TW"), // current
new String[] { "fa-Arab-AF-u-nu-latn", "zh-Hant-TW" })); // supported
-
+ assertEquals(LocaleList.forLanguageTags("en-US,zh-Hans-TW"),
+ SettingsHelper.resolveLocales(
+ LocaleList.forLanguageTags("en-UK,en-GB,zh-Hans-HK"), // restore
+ LocaleList.forLanguageTags("en-US,zh-Hans-TW"), // current
+ new String[] { "en-US,zh-Hans-TW,en-UK,en-GB,zh-Hans-HK" })); // supported
}
@Test
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7f4544379cd3..2b17ae48132c 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -220,6 +220,7 @@ filegroup {
"tests/src/**/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt",
"tests/src/**/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt",
"tests/src/**/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt",
"tests/src/**/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt",
@@ -356,7 +357,9 @@ filegroup {
"tests/src/**/systemui/qs/tiles/AlarmTileTest.kt",
"tests/src/**/systemui/qs/tiles/BluetoothTileTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt",
+ "tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt",
"tests/src/**/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt",
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index fb1f715bc68f..172f687ef6fd 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -40,6 +40,7 @@ android_app {
"com_android_systemui_flags_lib",
"SettingsLibDisplayUtils",
"SettingsLibSettingsTheme",
+ "SystemUI-shared-utils",
"com_android_a11y_menu_flags_lib",
"//frameworks/libs/systemui:view_capture",
],
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
index a60778658c59..db2fbd96408c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuOverlayLayout.java
@@ -22,8 +22,6 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
-
import static java.lang.Math.max;
import android.animation.Animator;
@@ -55,11 +53,11 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.UiContext;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
import java.util.ArrayList;
import java.util.List;
@@ -145,9 +143,7 @@ public class A11yMenuOverlayLayout {
final Display display = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
final Context uiContext = mService.createWindowContext(
display, TYPE_ACCESSIBILITY_OVERLAY, /* options= */null);
- final ViewCaptureAwareWindowManager windowManager =
- getViewCaptureAwareWindowManagerInstance(uiContext,
- com.android.systemui.Flags.enableViewCaptureTracing());
+ final WindowManager windowManager = WindowManagerUtils.getWindowManager(uiContext);
mLayout = new A11yMenuFrameLayout(uiContext);
updateLayoutPosition(uiContext);
inflateLayoutAndSetOnTouchListener(mLayout, uiContext);
@@ -162,8 +158,7 @@ public class A11yMenuOverlayLayout {
public void clearLayout() {
if (mLayout != null) {
- ViewCaptureAwareWindowManager windowManager = getViewCaptureAwareWindowManagerInstance(
- mLayout.getContext(), com.android.systemui.Flags.enableViewCaptureTracing());
+ WindowManager windowManager = WindowManagerUtils.getWindowManager(mLayout.getContext());
if (windowManager != null) {
windowManager.removeView(mLayout);
}
@@ -178,7 +173,7 @@ public class A11yMenuOverlayLayout {
return;
}
updateLayoutPosition(mLayout.getContext());
- WindowManager windowManager = mLayout.getContext().getSystemService(WindowManager.class);
+ WindowManager windowManager = WindowManagerUtils.getWindowManager(mLayout.getContext());
if (windowManager != null) {
windowManager.updateViewLayout(mLayout, mLayoutParameter);
}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
index b899c45b0f7e..842834b5fc27 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuViewPager.java
@@ -36,6 +36,7 @@ import com.android.systemui.accessibility.accessibilitymenu.R;
import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
import com.android.systemui.accessibility.accessibilitymenu.view.A11yMenuFooter.A11yMenuFooterCallBack;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
import java.util.ArrayList;
import java.util.List;
@@ -292,8 +293,8 @@ public class A11yMenuViewPager {
// Keeps footer window height unchanged no matter the density is changed.
mA11yMenuFooter.adjustFooterToDensityScale(densityScale);
// Adjust the view pager height for system bar and display cutout insets.
- WindowManager windowManager = mA11yMenuLayout.getContext()
- .getSystemService(WindowManager.class);
+ WindowManager windowManager = WindowManagerUtils
+ .getWindowManager(mA11yMenuLayout.getContext());
WindowMetrics windowMetric = windowManager.getCurrentWindowMetrics();
Insets windowInsets = windowMetric.getWindowInsets().getInsetsIgnoringVisibility(
WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
diff --git a/packages/SystemUI/aconfig/desktop_users_and_accounts.aconfig b/packages/SystemUI/aconfig/desktop_users_and_accounts.aconfig
new file mode 100644
index 000000000000..c7e9c9fbee2e
--- /dev/null
+++ b/packages/SystemUI/aconfig/desktop_users_and_accounts.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.systemui"
+container: "system"
+
+flag {
+ name: "user_switcher_add_sign_out_option"
+ namespace: "desktop_users_and_accounts"
+ description: "Add a sign out option to the user switcher menu if sign out is possible"
+ bug: "381478261"
+} \ No newline at end of file
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 30bce3576e4f..9da07170430f 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -289,6 +289,15 @@ flag {
}
}
+flag {
+ name: "notification_skip_silent_updates"
+ namespace: "systemui"
+ description: "Do not notify HeadsUpManager for silent updates."
+ bug: "401068530"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
flag {
name: "scene_container"
@@ -533,6 +542,14 @@ flag {
}
flag {
+ name: "icon_refresh_2025"
+ namespace: "systemui"
+ description: "Build time flag for 2025 icon refresh"
+ bug: "391605373"
+ is_fixed_read_only: true
+}
+
+flag {
name: "promote_notifications_automatically"
namespace: "systemui"
description: "Flag to automatically turn certain notifications into promoted notifications so "
@@ -1856,10 +1873,11 @@ flag {
}
flag {
- name: "shade_header_fonts"
+ name: "shade_header_font_update"
namespace: "systemui"
description: "Updates the fonts of the shade header"
- bug: "393609724"
+ bug: "393609960"
+ is_fixed_read_only: true
}
flag {
@@ -2012,7 +2030,14 @@ flag {
flag {
name: "permission_helper_ui_rich_ongoing"
namespace: "systemui"
- description: "[RONs] Guards inline permission helper for demoting RONs"
+ description: "[RONs] Guards inline permission helper for demoting RONs [Guts/card version]"
+ bug: "379186372"
+}
+
+flag {
+ name: "permission_helper_inline_ui_rich_ongoing"
+ namespace: "systemui"
+ description: "[RONs] Guards inline permission helper for demoting RONs [Inline version]"
bug: "379186372"
}
@@ -2138,3 +2163,26 @@ flag {
}
}
+flag {
+ name: "keyguard_wm_reorder_atms_calls"
+ namespace: "systemui"
+ description: "Calls ATMS#setLockScreenShown before default display callbacks in case they're slow"
+ bug: "399693427"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
+ name: "extended_apps_shortcut_category"
+ namespace: "systemui"
+ description: "Allow users to add shortcuts to open apps that are not present in the apps category in shortcut helper by default"
+ bug: "394290928"
+}
+
+flag {
+ name: "use_aad_prox_sensor"
+ namespace: "systemui"
+ description: "Use AAD proximity sensor if flag is enabled and sensor is present"
+ bug: "402534470"
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 5599db7689c2..440a81fc2152 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -751,7 +751,8 @@ constructor(
OriginTransition(createLongLivedRunner(controllerFactory, scope, forLaunch = true)),
"${cookie}_launchTransition",
)
- transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = true)
+ // TODO(b/403529740): re-enable takeovers once we solve the Compose jank issues.
+ transitionRegister.register(launchFilter, launchRemoteTransition, includeTakeover = false)
// Cross-task close transitions should not use this animation, so we only register it for
// when the opening window is Launcher.
@@ -777,7 +778,8 @@ constructor(
),
"${cookie}_returnTransition",
)
- transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = true)
+ // TODO(b/403529740): re-enable takeovers once we solve the Compose jank issues.
+ transitionRegister.register(returnFilter, returnRemoteTransition, includeTakeover = false)
longLivedTransitions[cookie] = Pair(launchRemoteTransition, returnRemoteTransition)
}
@@ -1086,9 +1088,8 @@ constructor(
if (!success) finishedCallback?.onAnimationFinished()
}
} else {
- // This should never happen, as either the controller or factory should always be
- // defined. This final call is for safety in case something goes wrong.
- Log.wtf(TAG, "initAndRun with neither a controller nor factory")
+ // This happens when onDisposed() has already been called due to the animation being
+ // cancelled. Only issue the callback.
finishedCallback?.onAnimationFinished()
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
index 1f98cd8e07c0..90311ed93987 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinInputDisplay.kt
@@ -35,7 +35,7 @@ import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
@@ -83,10 +83,7 @@ import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
@Composable
-fun PinInputDisplay(
- viewModel: PinBouncerViewModel,
- modifier: Modifier = Modifier,
-) {
+fun PinInputDisplay(viewModel: PinBouncerViewModel, modifier: Modifier = Modifier) {
val hintedPinLength: Int? by viewModel.hintedPinLength.collectAsStateWithLifecycle()
val shapeAnimations = rememberShapeAnimations(viewModel.pinShapes)
@@ -173,7 +170,10 @@ private fun HintingPinInputDisplay(
LaunchedEffect(Unit) { playAnimation = true }
val dotColor = MaterialTheme.colorScheme.onSurfaceVariant
- Row(modifier = modifier.heightIn(min = shapeAnimations.shapeSize)) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = modifier.height(shapeAnimations.shapeSize),
+ ) {
pinEntryDrawable.forEachIndexed { index, drawable ->
// Key the loop by [index] and [drawable], so that updating a shape drawable at the same
// index will play the new animation (by remembering a new [atEnd]).
@@ -316,17 +316,15 @@ private fun SimArea(viewModel: PinBouncerViewModel) {
Box(modifier = Modifier.padding(bottom = 20.dp)) {
// If isLockedEsim is null, then we do not show anything.
if (isLockedEsim == true) {
- PlatformOutlinedButton(
- onClick = { viewModel.onDisableEsimButtonClicked() },
- ) {
+ PlatformOutlinedButton(onClick = { viewModel.onDisableEsimButtonClicked() }) {
Row(
horizontalArrangement = Arrangement.spacedBy(10.dp),
- verticalAlignment = Alignment.CenterVertically
+ verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.ic_no_sim),
contentDescription = null,
- colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface)
+ colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
)
Text(
text = stringResource(R.string.disable_carrier_button_text),
@@ -339,15 +337,13 @@ private fun SimArea(viewModel: PinBouncerViewModel) {
Image(
painter = painterResource(id = R.drawable.ic_lockscreen_sim),
contentDescription = null,
- colorFilter = ColorFilter.tint(colorResource(id = R.color.background_protected))
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.background_protected)),
)
}
}
}
-private class PinInputRow(
- val shapeAnimations: ShapeAnimations,
-) {
+private class PinInputRow(val shapeAnimations: ShapeAnimations) {
private val entries = mutableStateListOf<PinInputEntry>()
@Composable
@@ -359,10 +355,11 @@ private class PinInputRow(
contentAlignment = Alignment.Center,
) {
Row(
- modifier
- .heightIn(min = shapeAnimations.shapeSize)
- // Pins overflowing horizontally should still be shown as scrolling.
- .wrapContentSize(unbounded = true)
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.height(shapeAnimations.shapeSize)
+ // Pins overflowing horizontally should still be shown as scrolling.
+ .wrapContentSize(unbounded = true),
) {
entries.forEach { entry -> key(entry.digit) { entry.Content() } }
}
@@ -439,10 +436,7 @@ private class PinInputRow(
}
}
-private class PinInputEntry(
- val digit: Digit,
- val shapeAnimations: ShapeAnimations,
-) {
+private class PinInputEntry(val digit: Digit, val shapeAnimations: ShapeAnimations) {
private val shape = shapeAnimations.getShapeToDot(digit.sequenceNumber)
// horizontal space occupied, used to shift contents as individual digits are animated in/out
private val entryWidth =
@@ -474,7 +468,7 @@ private class PinInputEntry(
suspend fun animateRemoval() = coroutineScope {
awaitAll(
async { entryWidth.animateTo(0.dp, shapeAnimations.inputShiftAnimationSpec) },
- async { shapeSize.animateTo(0.dp, shapeAnimations.deleteShapeSizeAnimationSpec) }
+ async { shapeSize.animateTo(0.dp, shapeAnimations.deleteShapeSizeAnimationSpec) },
)
}
@@ -505,7 +499,7 @@ private class PinInputEntry(
layout(animatedEntryWidth.roundToPx(), shapeHeight.roundToPx()) {
placeable.place(
((animatedEntryWidth - animatedShapeSize) / 2f).roundToPx(),
- ((shapeHeight - animatedShapeSize) / 2f).roundToPx()
+ ((shapeHeight - animatedShapeSize) / 2f).roundToPx(),
)
}
},
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 0db2bb51c971..5fac6863e931 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -33,6 +33,7 @@ import com.android.compose.animation.scene.ElementKey
import com.android.systemui.biometrics.AuthController
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.binder.DeviceEntryIconViewBinder
@@ -49,12 +50,14 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
class LockSection
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val windowManager: WindowManager,
private val authController: AuthController,
private val featureFlags: FeatureFlagsClassic,
@@ -80,6 +83,7 @@ constructor(
id = R.id.device_entry_icon_view
DeviceEntryIconViewBinder.bind(
applicationScope,
+ mainDispatcher,
this,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt
new file mode 100644
index 000000000000..bc38ef0abb25
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimFlingBehavior.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.notifications.ui.composable
+
+import androidx.compose.animation.core.AnimationState
+import androidx.compose.animation.core.DecayAnimationSpec
+import androidx.compose.animation.core.animateDecay
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.ui.MotionDurationScale
+import com.android.systemui.scene.session.ui.composable.rememberSession
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.withContext
+import kotlin.math.abs
+
+/**
+ * Fork of [androidx.compose.foundation.gestures.DefaultFlingBehavior] to allow us to use it with
+ * [rememberSession].
+ */
+internal class NotificationScrimFlingBehavior(
+ private var flingDecay: DecayAnimationSpec<Float>,
+ private val motionDurationScale: MotionDurationScale = NotificationScrimMotionDurationScale
+) : FlingBehavior {
+ override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+ // come up with the better threshold, but we need it since spline curve gives us NaNs
+ return withContext(motionDurationScale) {
+ if (abs(initialVelocity) > 1f) {
+ var velocityLeft = initialVelocity
+ var lastValue = 0f
+ val animationState =
+ AnimationState(
+ initialValue = 0f,
+ initialVelocity = initialVelocity,
+ )
+ try {
+ animationState.animateDecay(flingDecay) {
+ val delta = value - lastValue
+ val consumed = scrollBy(delta)
+ lastValue = value
+ velocityLeft = this.velocity
+ // avoid rounding errors and stop if anything is unconsumed
+ if (abs(delta - consumed) > 0.5f) this.cancelAnimation()
+ }
+ } catch (exception: CancellationException) {
+ velocityLeft = animationState.velocity
+ }
+ velocityLeft
+ } else {
+ initialVelocity
+ }
+ }
+ }
+}
+
+internal val NotificationScrimMotionDurationScale =
+ object : MotionDurationScale {
+ override val scaleFactor: Float
+ get() = 1f
+ } \ No newline at end of file
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 09b8d178cc8e..79b346439d5d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -20,12 +20,13 @@ package com.android.systemui.notifications.ui.composable
import android.util.Log
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
+import androidx.compose.animation.core.DecayAnimationSpec
import androidx.compose.animation.core.tween
+import androidx.compose.animation.splineBasedDecay
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollBy
@@ -59,7 +60,6 @@ import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
@@ -97,6 +97,7 @@ import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadi
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
import com.android.systemui.scene.session.ui.composable.rememberSession
+import com.android.systemui.scene.session.ui.composable.sessionCoroutineScope
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.ui.composable.ShadeHeader
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
@@ -298,7 +299,7 @@ fun ContentScope.NotificationScrollingStack(
onEmptySpaceClick: (() -> Unit)? = null,
modifier: Modifier = Modifier,
) {
- val coroutineScope = rememberCoroutineScope()
+ val coroutineScope = shadeSession.sessionCoroutineScope()
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
val scrimCornerRadius = dimensionResource(R.dimen.notification_scrim_corner_radius)
@@ -308,8 +309,6 @@ fun ContentScope.NotificationScrollingStack(
ScrollState(initial = 0)
}
val syntheticScroll = viewModel.syntheticScroll.collectAsStateWithLifecycle(0f)
- val isCurrentGestureOverscroll =
- viewModel.isCurrentGestureOverscroll.collectAsStateWithLifecycle(false)
val expansionFraction by viewModel.expandFraction.collectAsStateWithLifecycle(0f)
val shadeToQsFraction by viewModel.shadeToQsFraction.collectAsStateWithLifecycle(0f)
@@ -454,15 +453,15 @@ fun ContentScope.NotificationScrollingStack(
}
}
- val flingBehavior = ScrollableDefaults.flingBehavior()
val scrimNestedScrollConnection =
shadeSession.rememberSession(
scrimOffset,
- maxScrimTop,
minScrimTop,
- isCurrentGestureOverscroll,
- flingBehavior,
+ viewModel.isCurrentGestureOverscroll,
+ density,
) {
+ val flingSpec: DecayAnimationSpec<Float> = splineBasedDecay(density)
+ val flingBehavior = NotificationScrimFlingBehavior(flingSpec)
NotificationScrimNestedScrollConnection(
scrimOffset = { scrimOffset.value },
snapScrimOffset = { value -> coroutineScope.launch { scrimOffset.snapTo(value) } },
@@ -473,7 +472,7 @@ fun ContentScope.NotificationScrollingStack(
maxScrimOffset = 0f,
contentHeight = { stackHeight.intValue.toFloat() },
minVisibleScrimHeight = minVisibleScrimHeight,
- isCurrentGestureOverscroll = { isCurrentGestureOverscroll.value },
+ isCurrentGestureOverscroll = { viewModel.isCurrentGestureOverscroll },
flingBehavior = flingBehavior,
)
}
@@ -574,11 +573,11 @@ fun ContentScope.NotificationScrollingStack(
) {
Column(
modifier =
- Modifier.thenIf(supportNestedScrolling) {
+ Modifier.disableSwipesWhenScrolling(NestedScrollableBound.BottomRight)
+ .thenIf(supportNestedScrolling) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
.stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
- .disableSwipesWhenScrolling(NestedScrollableBound.BottomRight)
.verticalScroll(scrollState)
.padding(top = stackTopPadding, bottom = stackBottomPadding)
.fillMaxWidth()
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
index 4eaacf31e23d..1750b11a998b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
@@ -17,10 +17,14 @@
package com.android.systemui.scene.session.ui.composable
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffectResult
+import androidx.compose.runtime.DisposableEffectScope
+import androidx.compose.runtime.RememberObserver
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.currentCompositeKeyHash
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCompositionContext
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.saveable.mapSaver
@@ -28,6 +32,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.android.systemui.scene.session.shared.SessionStorage
import com.android.systemui.util.kotlin.mapValuesNotNullTo
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
/**
* An explicit storage for remembering composable state outside of the lifetime of a composition.
@@ -89,6 +97,55 @@ fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init:
rememberSession(key, *inputs, init = init)
/**
+ * A side effect of composition that must be reversed or cleaned up if the [Session] ends.
+ *
+ * @see androidx.compose.runtime.DisposableEffect
+ */
+@Composable
+fun Session.SessionDisposableEffect(
+ vararg inputs: Any?,
+ key: String? = null,
+ effect: DisposableEffectScope.() -> DisposableEffectResult,
+) {
+ rememberSession(inputs, key) {
+ object : RememberObserver {
+
+ var onDispose: DisposableEffectResult? = null
+
+ override fun onAbandoned() {
+ // no-op
+ }
+
+ override fun onForgotten() {
+ onDispose?.dispose()
+ onDispose = null
+ }
+
+ override fun onRemembered() {
+ onDispose = DisposableEffectScope().effect()
+ }
+ }
+ }
+}
+
+/**
+ * Return a [CoroutineScope] bound to this [Session] using the optional [CoroutineContext] provided
+ * by [getContext]. [getContext] will only be called once and the same [CoroutineScope] instance
+ * will be returned for the duration of the [Session].
+ *
+ * @see androidx.compose.runtime.rememberCoroutineScope
+ */
+@Composable
+fun Session.sessionCoroutineScope(
+ getContext: () -> CoroutineContext = { EmptyCoroutineContext }
+): CoroutineScope {
+ val effectContext: CoroutineContext = rememberCompositionContext().effectCoroutineContext
+ val job = rememberSession { Job() }
+ SessionDisposableEffect { onDispose { job.cancel() } }
+ return rememberSession { CoroutineScope(effectContext + job + getContext()) }
+}
+
+/**
* An explicit storage for remembering composable state outside of the lifetime of a composition.
*
* Specifically, this allows easy conversion of standard [rememberSession] invocations to ones that
@@ -147,15 +204,10 @@ interface SaveableSession : Session {
* location in the composition tree.
*/
@Composable
-fun rememberSaveableSession(
- vararg inputs: Any?,
- key: String? = null,
-): SaveableSession =
+fun rememberSaveableSession(vararg inputs: Any?, key: String? = null): SaveableSession =
rememberSaveable(*inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
-private class SessionImpl(
- private val storage: SessionStorage = SessionStorage(),
-) : Session {
+private class SessionImpl(private val storage: SessionStorage = SessionStorage()) : Session {
@Composable
override fun <T> rememberSession(key: String?, vararg inputs: Any?, init: () -> T): T {
val storage = storage.storage
@@ -169,16 +221,31 @@ private class SessionImpl(
}
if (finalKey !in storage) {
val value = init()
- SideEffect { storage[finalKey] = SessionStorage.StorageEntry(inputs, value) }
+ SideEffect {
+ storage[finalKey] = SessionStorage.StorageEntry(inputs, value)
+ if (value is RememberObserver) {
+ value.onRemembered()
+ }
+ }
return value
}
val entry = storage[finalKey]!!
if (!inputs.contentEquals(entry.keys)) {
val value = init()
- SideEffect { entry.stored = value }
+ SideEffect {
+ val oldValue = entry.stored
+ if (oldValue is RememberObserver) {
+ oldValue.onForgotten()
+ }
+ entry.stored = value
+ if (value is RememberObserver) {
+ value.onRemembered()
+ }
+ }
return value
}
- @Suppress("UNCHECKED_CAST") return entry.stored as T
+ @Suppress("UNCHECKED_CAST")
+ return entry.stored as T
}
}
@@ -228,7 +295,8 @@ private class SaveableSessionImpl(
}
return value
}
- @Suppress("UNCHECKED_CAST") return entry.stored as T
+ @Suppress("UNCHECKED_CAST")
+ return entry.stored as T
}
}
}
@@ -263,7 +331,7 @@ private class SaveableSessionImpl(
v?.let { StorageEntry.Unrestored(v) }
}
)
- }
+ },
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 068218a0053a..b30e12f073ad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -55,6 +55,7 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.mechanics.behavior.VerticalExpandContainerSpec
import com.android.mechanics.behavior.verticalExpandContainerBackground
+import com.android.systemui.Flags
import com.android.systemui.res.R
import com.android.systemui.shade.ui.composable.OverlayShade.rememberShadeExpansionMotion
@@ -189,9 +190,17 @@ object OverlayShade {
}
object Colors {
- val ScrimBackground = Color(0f, 0f, 0f, alpha = 0.2f)
+ val ScrimBackground = Color(0f, 0f, 0f, alpha = 0.3f)
val PanelBackground: Color
- @Composable @ReadOnlyComposable get() = MaterialTheme.colorScheme.surfaceContainer
+ @Composable
+ @ReadOnlyComposable
+ get() {
+ return if (Flags.notificationShadeBlur()) {
+ MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.4f)
+ } else {
+ MaterialTheme.colorScheme.surfaceContainer
+ }
+ }
}
object Dimensions {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index addb08fd2319..03debb6fa7ca 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -293,6 +293,25 @@ fun ContentScope.OverlayShadeHeader(
viewModel: ShadeHeaderViewModel,
modifier: Modifier = Modifier,
) {
+ OverlayShadeHeaderPartialStateless(
+ viewModel,
+ viewModel.showClock,
+ modifier,
+ )
+}
+
+/**
+ * Ideally, we should have a stateless function for overlay shade header, which facilitates testing.
+ * However, it is cumbersome to implement such a stateless function, especially when some of the
+ * overlay shade header's children accept a view model as the param. Therefore, this function only
+ * break up the clock visibility. It is where "PartialStateless" comes from.
+ */
+@Composable
+fun ContentScope.OverlayShadeHeaderPartialStateless(
+ viewModel: ShadeHeaderViewModel,
+ showClock: Boolean,
+ modifier: Modifier = Modifier,
+) {
val horizontalPadding =
max(LocalScreenCornerRadius.current / 2f, Shade.Dimensions.HorizontalPadding)
@@ -308,7 +327,7 @@ fun ContentScope.OverlayShadeHeader(
modifier = Modifier.padding(horizontal = horizontalPadding),
) {
val chipHighlight = viewModel.notificationsChipHighlight
- if (viewModel.showClock) {
+ if (showClock) {
Clock(
onClick = viewModel::onClockClicked,
modifier = Modifier.padding(horizontal = 4.dp),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
index f9492dad85df..a65afad9cb7a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/volume/ui/composable/VolumeSlider.kt
@@ -127,6 +127,8 @@ fun VolumeSlider(
SliderDefaults.colors(
activeTickColor = MaterialTheme.colorScheme.surfaceContainerHigh,
inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ disabledActiveTickColor = MaterialTheme.colorScheme.surfaceContainerHigh,
+ disabledInactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHigh,
)
Slider(
value = state.value,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 2845f6a2983a..e75f60736435 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -507,8 +507,8 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
0 /* flags */);
users.add(new UserRecord(info, null, false /* isGuest */, false /* isCurrent */,
false /* isAddUser */, false /* isRestricted */, true /* isSwitchToEnabled */,
- false /* isAddSupervisedUser */, null /* enforcedAdmin */,
- false /* isManageUsers */));
+ false /* isAddSupervisedUser */, false /* isSignOut */,
+ null /* enforcedAdmin */, false /* isManageUsers */));
}
return users;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
index aa95abb3528f..0b0088926aae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationModeSwitchTest.java
@@ -73,14 +73,10 @@ import android.widget.ImageView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
-import kotlin.Lazy;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -108,8 +104,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
private SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@Mock
private MagnificationModeSwitch.ClickListener mClickListener;
- @Mock
- private Lazy<ViewCapture> mLazyViewCapture;
private TestableWindowManager mWindowManager;
private ViewPropertyAnimator mViewPropertyAnimator;
private MagnificationModeSwitch mMagnificationModeSwitch;
@@ -123,7 +117,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
mContext = Mockito.spy(getContext());
final WindowManager wm = mContext.getSystemService(WindowManager.class);
mWindowManager = spy(new TestableWindowManager(wm));
- mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
mSpyImageView = Mockito.spy(new ImageView(mContext));
mViewPropertyAnimator = Mockito.spy(mSpyImageView.animate());
@@ -139,10 +132,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
return null;
}).when(mSfVsyncFrameProvider).postFrameCallback(
any(Choreographer.FrameCallback.class));
- ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, false);
- mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView,
- mSfVsyncFrameProvider, mClickListener, vwm);
+ mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mWindowManager,
+ mSpyImageView, mSfVsyncFrameProvider, mClickListener);
assertNotNull(mTouchListener);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
index 3cd3fefb8ef0..02ec5aac120c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MagnificationSettingsControllerTest.java
@@ -23,11 +23,11 @@ import static org.mockito.Mockito.verify;
import android.content.pm.ActivityInfo;
import android.testing.TestableLooper;
+import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize;
@@ -58,15 +58,15 @@ public class MagnificationSettingsControllerTest extends SysuiTestCase {
@Mock
private SecureSettings mSecureSettings;
@Mock
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private WindowManager mWindowManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mMagnificationSettingsController = new MagnificationSettingsController(
mContext, mSfVsyncFrameProvider,
- mMagnificationSettingControllerCallback, mSecureSettings,
- mWindowMagnificationSettings, mViewCaptureAwareWindowManager);
+ mMagnificationSettingControllerCallback, mSecureSettings, mWindowManager,
+ mWindowMagnificationSettings);
}
@After
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
index 12c866f0adb2..463bfe5ae73f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/MirrorWindowControlTest.java
@@ -27,14 +27,15 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.Point;
+import android.os.Looper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
@@ -48,7 +49,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class MirrorWindowControlTest extends SysuiTestCase {
- @Mock ViewCaptureAwareWindowManager mWindowManager;
+ @Mock WindowManager mWindowManager;
View mView;
int mViewWidth;
int mViewHeight;
@@ -69,8 +70,12 @@ public class MirrorWindowControlTest extends SysuiTestCase {
return null;
}).when(mWindowManager).addView(any(View.class), any(LayoutParams.class));
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+
mStubMirrorWindowControl = new StubMirrorWindowControl(getContext(), mView, mViewWidth,
- mViewHeight);
+ mViewHeight, mWindowManager);
}
@Test
@@ -122,8 +127,9 @@ public class MirrorWindowControlTest extends SysuiTestCase {
boolean mInvokeOnCreateView = false;
- StubMirrorWindowControl(Context context, View view, int width, int height) {
- super(context);
+ StubMirrorWindowControl(Context context, View view, int width, int height,
+ WindowManager windowManager) {
+ super(context, windowManager);
mView = view;
mWidth = width;
mHeight = height;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
index e1e515eb31f5..b1c837a99f3d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/ModeSwitchesControllerTest.java
@@ -24,11 +24,11 @@ import android.provider.Settings;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.View;
+import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import org.junit.After;
@@ -51,8 +51,6 @@ public class ModeSwitchesControllerTest extends SysuiTestCase {
private View mSpyView;
@Mock
private MagnificationModeSwitch.ClickListener mListener;
- @Mock
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@Before
@@ -61,8 +59,9 @@ public class ModeSwitchesControllerTest extends SysuiTestCase {
mSupplier = new FakeSwitchSupplier(mContext.getSystemService(DisplayManager.class));
mModeSwitchesController = new ModeSwitchesController(mSupplier);
mModeSwitchesController.setClickListenerDelegate(mListener);
- mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, mModeSwitchesController,
- mViewCaptureAwareWindowManager));
+ WindowManager wm = mContext.getSystemService(WindowManager.class);
+ mModeSwitch = Mockito.spy(new MagnificationModeSwitch(mContext, wm,
+ mModeSwitchesController));
mSpyView = Mockito.spy(new View(mContext));
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 6edf94939010..95ebd8190e9c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -72,7 +72,7 @@ public class DragToInteractAnimationControllerTest extends SysuiTestCase {
stubWindowManager);
final MenuView stubMenuView = spy(new MenuView(mContext, stubMenuViewModel,
stubMenuViewAppearance, mockSecureSettings));
- mInteractView = spy(new DragToInteractView(mContext));
+ mInteractView = spy(new DragToInteractView(mContext, stubWindowManager));
mDismissView = spy(new DismissView(mContext));
if (Flags.floatingMenuDragToEdit()) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index fff6def52803..572d140b850d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -101,7 +101,7 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
mStubMenuView.setTranslationY(0);
mMenuAnimationController = spy(new MenuAnimationController(
mStubMenuView, stubMenuViewAppearance));
- mInteractView = spy(new DragToInteractView(mContext));
+ mInteractView = spy(new DragToInteractView(mContext, windowManager));
mDismissView = spy(new DismissView(mContext));
if (Flags.floatingMenuDragToEdit()) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 4f043109a534..b5fa52570d6a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -39,15 +39,11 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.settingslib.bluetooth.HearingAidDeviceManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.util.settings.SecureSettings;
-import kotlin.Lazy;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -78,16 +74,11 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
@Mock
private WindowMetrics mWindowMetrics;
- @Mock
- private Lazy<ViewCapture> mLazyViewCapture;
-
private MenuViewLayerController mMenuViewLayerController;
@Before
public void setUp() throws Exception {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
- final ViewCaptureAwareWindowManager viewCaptureAwareWm = new ViewCaptureAwareWindowManager(
- mWindowManager, mLazyViewCapture, /* isViewCaptureEnabled= */ false);
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
@@ -95,8 +86,8 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
- viewCaptureAwareWm, mAccessibilityManager, mSecureSettings,
- mock(NavigationModeController.class), mHearingAidDeviceManager);
+ mAccessibilityManager, mSecureSettings, mock(NavigationModeController.class),
+ mHearingAidDeviceManager);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
index d118ace08b85..530aba4993a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java
@@ -176,6 +176,8 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase {
.thenReturn(SETTINGS_PACKAGE_NAME);
when(mDevice.getBondState()).thenReturn(BOND_BONDED);
when(mDevice.isConnected()).thenReturn(true);
+ when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
+ when(mDevice.getAnonymizedAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(DEVICE_NAME);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 446891a7873e..c4fafc192260 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -41,7 +41,6 @@ import android.widget.ScrollView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
@@ -116,7 +115,6 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Mock private lateinit var packageManager: PackageManager
@Mock private lateinit var activityTaskManager: ActivityTaskManager
@Mock private lateinit var accessibilityManager: AccessibilityManager
- @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
private lateinit var displayRepository: FakeDisplayRepository
private lateinit var displayStateInteractor: DisplayStateInteractor
@@ -689,7 +687,6 @@ open class AuthContainerViewTest : SysuiTestCase() {
{ credentialViewModel },
fakeExecutor,
vibrator,
- lazyViewCapture,
msdlPlayer,
) {
override fun postOnAnimation(runnable: Runnable) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
index a1a2aa70d869..f4185ee48510 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -83,7 +83,6 @@ import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
import com.android.internal.R;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
@@ -100,11 +99,10 @@ import com.android.systemui.util.concurrency.Execution;
import com.android.systemui.util.concurrency.FakeExecution;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.google.android.msdl.domain.MSDLPlayer;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -175,8 +173,6 @@ public class AuthControllerTest extends SysuiTestCase {
private PromptViewModel mPromptViewModel;
@Mock
private UdfpsUtils mUdfpsUtils;
- @Mock
- private Lazy<ViewCapture> mLazyViewCapture;
@Captor
private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mFpAuthenticatorsRegisteredCaptor;
@@ -198,6 +194,8 @@ public class AuthControllerTest extends SysuiTestCase {
private KeyguardManager mKeyguardManager;
@Mock
private MSDLPlayer mMSDLPlayer;
+ @Mock
+ private WindowManagerProvider mWindowManagerProvider;
private TestableContext mContextSpy;
private Execution mExecution;
@@ -1198,7 +1196,8 @@ public class AuthControllerTest extends SysuiTestCase {
when(mDisplayManager.getDisplay(displayId)).thenReturn(mockDisplay);
}
doReturn(mockDisplayContext).when(mContextSpy).createDisplayContext(mockDisplay);
- when(mockDisplayContext.getSystemService(WindowManager.class)).thenReturn(mockDisplayWM);
+ when(mWindowManagerProvider.getWindowManager(mockDisplayContext))
+ .thenReturn(mockDisplayWM);
return mockDisplayWM;
}
@@ -1214,7 +1213,7 @@ public class AuthControllerTest extends SysuiTestCase {
() -> mLogContextInteractor, () -> mPromptSelectionInteractor,
() -> mCredentialViewModel, () -> mPromptViewModel, mInteractionJankMonitor,
mHandler, mBackgroundExecutor, mUdfpsUtils, mVibratorHelper, mKeyguardManager,
- mLazyViewCapture, mMSDLPlayer);
+ mMSDLPlayer, mWindowManagerProvider);
}
@Override
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a2f5a30a20ff..675c9deaff23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -45,12 +45,12 @@ import android.view.LayoutInflater;
import android.view.Surface;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.util.LatencyTracker;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -129,7 +129,7 @@ public class UdfpsControllerTest extends SysuiTestCase {
@Mock
private FingerprintManager mFingerprintManager;
@Mock
- private ViewCaptureAwareWindowManager mWindowManager;
+ private WindowManager mWindowManager;
@Mock
private StatusBarStateController mStatusBarStateController;
@Mock
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index c1feca29906a..91ec1cbce8a4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -77,6 +77,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
private lateinit var resources: TestableResources
private lateinit var trustRepository: FakeTrustRepository
private lateinit var testScope: TestScope
+ private val TEST_REASON = "reason"
@Before
fun setUp() {
@@ -118,7 +119,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
// WHEN bouncer show is requested
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
// WHEN all queued messages are dispatched
mainHandler.dispatchQueuedMessages()
@@ -134,7 +135,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testShow_isScrimmed() {
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
verify(repository).setKeyguardAuthenticatedBiometrics(null)
verify(repository).setPrimaryStartingToHide(false)
verify(repository).setPrimaryScrimmed(true)
@@ -162,7 +163,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testShowReturnsFalseWhenDelegateIsNotSet() {
whenever(bouncerView.delegate).thenReturn(null)
- assertThat(underTest.show(true)).isEqualTo(false)
+ assertThat(underTest.show(true, TEST_REASON)).isEqualTo(false)
}
@Test
@@ -171,7 +172,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.SimPuk)
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
verify(repository).setPrimaryShow(false)
verify(repository).setPrimaryShow(true)
}
@@ -352,7 +353,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(true)
// WHEN bouncer show is requested
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
// THEN primary show & primary showing soon aren't updated immediately
verify(repository, never()).setPrimaryShow(true)
@@ -375,7 +376,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
whenever(faceAuthInteractor.canFaceAuthRun()).thenReturn(false)
// WHEN bouncer show is requested
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
// THEN primary show & primary showing soon are updated immediately
verify(repository).setPrimaryShow(true)
@@ -394,7 +395,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
runCurrent()
// WHEN bouncer show is requested
- underTest.show(true)
+ underTest.show(true, TEST_REASON)
// THEN primary show & primary showing soon were scheduled to update
verify(repository, never()).setPrimaryShow(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index b42eddd12e4e..fd99313a17b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -36,7 +36,6 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.app.viewcapture.ViewCaptureFactory
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.internal.logging.UiEventLogger
@@ -143,7 +142,6 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
mock<ScrimManager> { on { currentController }.thenReturn(mScrimController) }
private val mSystemDialogsCloser = mock<SystemDialogsCloser>()
private val mDreamOverlayCallbackController = mock<DreamOverlayCallbackController>()
- private val mLazyViewCapture = lazy { viewCaptureSpy }
private val mViewCaptor = argumentCaptor<View>()
private val mTouchHandlersCaptor = argumentCaptor<Set<TouchHandler>>()
@@ -156,7 +154,6 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
private val gestureInteractor = spy(kosmos.gestureInteractor)
private lateinit var mCommunalInteractor: CommunalInteractor
- private lateinit var mViewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
private lateinit var environmentComponents: EnvironmentComponents
private lateinit var mService: DreamOverlayService
@@ -244,18 +241,12 @@ class DreamOverlayServiceTest(flags: FlagsParameterization?) : SysuiTestCase() {
mComplicationComponentFactory,
mAmbientTouchComponentFactory,
)
- mViewCaptureAwareWindowManager =
- ViewCaptureAwareWindowManager(
- mWindowManager,
- mLazyViewCapture,
- isViewCaptureEnabled = false,
- )
mService =
DreamOverlayService(
mContext,
mLifecycleOwner,
mMainExecutor,
- mViewCaptureAwareWindowManager,
+ mWindowManager,
mComplicationComponentFactory,
mDreamComplicationComponentFactory,
mDreamOverlayComponentFactory,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
index 63229dbb47a4..8fdbf9b45d2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt
@@ -229,7 +229,6 @@ class FromAlternateBouncerTransitionInteractorTest(flags: FlagsParameterization)
}
@Test
- @EnableFlags(FLAG_GLANCEABLE_HUB_V2)
fun transitionToDreaming() =
kosmos.runTest {
fakePowerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 0718d0d32812..83fd4c258082 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -264,6 +264,29 @@ class KeyguardInteractorTest : SysuiTestCase() {
}
@Test
+ fun dismissAlpha_doesNotEmitWhenNotDismissible() =
+ testScope.runTest {
+ val dismissAlpha by collectValues(underTest.dismissAlpha)
+ assertThat(dismissAlpha[0]).isEqualTo(1f)
+ assertThat(dismissAlpha.size).isEqualTo(1)
+
+ keyguardTransitionRepository.sendTransitionSteps(from = AOD, to = LOCKSCREEN, testScope)
+
+ // User begins to swipe up when not dimissible, which would show bouncer
+ repository.setStatusBarState(StatusBarState.KEYGUARD)
+ repository.setKeyguardDismissible(false)
+ shadeRepository.setLegacyShadeExpansion(0.98f)
+
+ assertThat(dismissAlpha[0]).isEqualTo(1f)
+ assertThat(dismissAlpha.size).isEqualTo(1)
+
+ // Shade reset should not affect dismiss alpha when not dismissible
+ shadeRepository.setLegacyShadeExpansion(0f)
+ assertThat(dismissAlpha[0]).isEqualTo(1f)
+ assertThat(dismissAlpha.size).isEqualTo(1)
+ }
+
+ @Test
fun dismissAlpha_onGlanceableHub_doesNotEmitWhenShadeResets() =
testScope.runTest {
val dismissAlpha by collectValues(underTest.dismissAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
index 6704d63395ad..582666561be2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractorTest.kt
@@ -267,12 +267,20 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() {
// action down: does NOT collapse the shade
val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
- verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ verify(statusBarKeyguardViewManager, never())
+ .showPrimaryBouncer(
+ any(),
+ eq("KeyguardKeyEventInteractor#collapseShadeLockedOrShowPrimaryBouncer"),
+ )
// action up: collapses the shade
val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isTrue()
- verify(statusBarKeyguardViewManager).showPrimaryBouncer(eq(true))
+ verify(statusBarKeyguardViewManager)
+ .showPrimaryBouncer(
+ eq(true),
+ eq("KeyguardKeyEventInteractor#collapseShadeLockedOrShowPrimaryBouncer"),
+ )
}
private fun verifyActionsDoNothing(keycode: Int) {
@@ -280,12 +288,20 @@ class KeyguardKeyEventInteractorTest : SysuiTestCase() {
val actionDownMenuKeyEvent = KeyEvent(KeyEvent.ACTION_DOWN, keycode)
assertThat(underTest.dispatchKeyEvent(actionDownMenuKeyEvent)).isFalse()
verify(shadeController, never()).animateCollapseShadeForced()
- verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ verify(statusBarKeyguardViewManager, never())
+ .showPrimaryBouncer(
+ any(),
+ eq("KeyguardKeyEventInteractor#collapseShadeLockedOrShowPrimaryBouncer"),
+ )
// action up: doesNothing
val actionUpMenuKeyEvent = KeyEvent(KeyEvent.ACTION_UP, keycode)
assertThat(underTest.dispatchKeyEvent(actionUpMenuKeyEvent)).isFalse()
verify(shadeController, never()).animateCollapseShadeForced()
- verify(statusBarKeyguardViewManager, never()).showPrimaryBouncer(any())
+ verify(statusBarKeyguardViewManager, never())
+ .showPrimaryBouncer(
+ any(),
+ eq("KeyguardKeyEventInteractor#collapseShadeLockedOrShowPrimaryBouncer"),
+ )
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index f0eedee48e57..4f351143c793 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -340,6 +340,22 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
@Test
@EnableSceneContainer
+ fun surfaceBehindVisibility_whileSceneContainerNotVisible_alwaysTrue() =
+ testScope.runTest {
+ val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(isSurfaceBehindVisible).isFalse()
+
+ kosmos.sceneInteractor.setVisible(false, "test")
+ runCurrent()
+
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(isSurfaceBehindVisible).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
fun surfaceBehindVisibility_idleWhileLocked_alwaysFalse() =
testScope.runTest {
val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index e1323c166f6b..9aee4c97f214 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.google.common.collect.Range
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -56,7 +57,8 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
fun onTapped() =
testScope.runTest {
underTest.onTapped()
- verify(statusBarKeyguardViewManager).showPrimaryBouncer(any())
+ verify(statusBarKeyguardViewManager)
+ .showPrimaryBouncer(any(), eq("AlternateBouncerViewModel#onTapped"))
}
@Test
@@ -154,7 +156,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
private fun stepToAlternateBouncer(
value: Float,
- state: TransitionState = TransitionState.RUNNING
+ state: TransitionState = TransitionState.RUNNING,
): TransitionStep {
return step(
from = KeyguardState.LOCKSCREEN,
@@ -166,7 +168,7 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
private fun stepFromAlternateBouncer(
value: Float,
- state: TransitionState = TransitionState.RUNNING
+ state: TransitionState = TransitionState.RUNNING,
): TransitionStep {
return step(
from = KeyguardState.ALTERNATE_BOUNCER,
@@ -180,14 +182,14 @@ class AlternateBouncerViewModelTest : SysuiTestCase() {
from: KeyguardState,
to: KeyguardState,
value: Float,
- transitionState: TransitionState
+ transitionState: TransitionState,
): TransitionStep {
return TransitionStep(
from = from,
to = to,
value = value,
transitionState = transitionState,
- ownerName = "AlternateBouncerViewModelTest"
+ ownerName = "AlternateBouncerViewModelTest",
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
index 8a11be0ab8fa..b1427f21345c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/data/repository/MediaFilterRepositoryTest.kt
@@ -219,17 +219,13 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
underTest.addSelectedUserMediaEntry(playingData1)
underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId1))
underTest.addSelectedUserMediaEntry(playingData2)
- underTest.addMediaDataLoadingState(
- MediaDataLoadingModel.Loaded(playingInstanceId2, false)
- )
+ underTest.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(playingInstanceId2))
assertThat(currentMedia?.size).isEqualTo(2)
assertThat(currentMedia)
.containsExactly(
MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)),
- MediaCommonModel.MediaControl(
- MediaDataLoadingModel.Loaded(playingInstanceId2, false)
- ),
+ MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)),
)
.inOrder()
@@ -238,9 +234,7 @@ class MediaFilterRepositoryTest : SysuiTestCase() {
assertThat(currentMedia?.size).isEqualTo(2)
assertThat(currentMedia)
.containsExactly(
- MediaCommonModel.MediaControl(
- MediaDataLoadingModel.Loaded(playingInstanceId2, false)
- ),
+ MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId2)),
MediaCommonModel.MediaControl(MediaDataLoadingModel.Loaded(playingInstanceId1)),
)
.inOrder()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
index faa62c2febc1..45128e23c900 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/util/MediaDiffUtilTest.kt
@@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
-import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.ui.viewmodel.mediaControlViewModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -37,8 +37,8 @@ class MediaDiffUtilTest : SysuiTestCase() {
@Test
fun newMediaControlAdded() {
- val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
- val oldList = listOf<MediaCommonViewModel>()
+ val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
+ val oldList = listOf<MediaControlViewModel>()
val newList = listOf(mediaControl)
val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
val mediaLoadedListUpdateCallback =
@@ -56,18 +56,18 @@ class MediaDiffUtilTest : SysuiTestCase() {
@Test
fun updateMediaControl_contentChanged() {
- val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
+ val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
val oldList = listOf(mediaControl)
- val newList = listOf(mediaControl.copy(immediatelyUpdateUi = false))
+ val newList = listOf(mediaControl.copy())
val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
val mediaLoadedListUpdateCallback =
MediaViewModelListUpdateCallback(
oldList,
newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> assertThat(commonViewModel).isNotEqualTo(mediaControl) },
+ { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
+ { controlViewModel, _ -> assertThat(controlViewModel).isNotEqualTo(mediaControl) },
{ fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
+ { controlViewModel, _, _ -> fail("Unexpected to move $controlViewModel ") },
)
DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
@@ -75,8 +75,8 @@ class MediaDiffUtilTest : SysuiTestCase() {
@Test
fun mediaControlMoved() {
- val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123), true)
- val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456), false)
+ val mediaControl1 = createMediaControl(InstanceId.fakeInstanceId(123))
+ val mediaControl2 = createMediaControl(InstanceId.fakeInstanceId(456))
val oldList = listOf(mediaControl1, mediaControl2)
val newList = listOf(mediaControl2, mediaControl1)
val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
@@ -84,10 +84,10 @@ class MediaDiffUtilTest : SysuiTestCase() {
MediaViewModelListUpdateCallback(
oldList,
newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
+ { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
+ { controlViewModel, _ -> fail("Unexpected to update $controlViewModel") },
{ fail("Unexpected to remove $it") },
- { commonViewModel, _, _ -> assertThat(commonViewModel).isEqualTo(mediaControl1) },
+ { controlViewModel, _, _ -> assertThat(controlViewModel).isEqualTo(mediaControl1) },
)
DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
@@ -95,34 +95,24 @@ class MediaDiffUtilTest : SysuiTestCase() {
@Test
fun mediaControlRemoved() {
- val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123), true)
+ val mediaControl = createMediaControl(InstanceId.fakeInstanceId(123))
val oldList = listOf(mediaControl)
- val newList = listOf<MediaCommonViewModel>()
+ val newList = listOf<MediaControlViewModel>()
val mediaLoadedCallback = MediaViewModelCallback(oldList, newList)
val mediaLoadedListUpdateCallback =
MediaViewModelListUpdateCallback(
oldList,
newList,
- { commonViewModel, _ -> fail("Unexpected to add $commonViewModel") },
- { commonViewModel, _ -> fail("Unexpected to update $commonViewModel") },
- { commonViewModel -> assertThat(commonViewModel).isEqualTo(mediaControl) },
- { commonViewModel, _, _ -> fail("Unexpected to move $commonViewModel ") },
+ { controlViewModel, _ -> fail("Unexpected to add $controlViewModel") },
+ { controlViewModel, _ -> fail("Unexpected to update $controlViewModel") },
+ { controlViewModel -> assertThat(controlViewModel).isEqualTo(mediaControl) },
+ { controlViewModel, _, _ -> fail("Unexpected to move $controlViewModel ") },
)
DiffUtil.calculateDiff(mediaLoadedCallback).dispatchUpdatesTo(mediaLoadedListUpdateCallback)
}
- private fun createMediaControl(
- instanceId: InstanceId,
- immediatelyUpdateUi: Boolean,
- ): MediaCommonViewModel.MediaControl {
- return MediaCommonViewModel.MediaControl(
- instanceId = instanceId,
- immediatelyUpdateUi = immediatelyUpdateUi,
- controlViewModel = kosmos.mediaControlViewModel,
- onAdded = {},
- onRemoved = {},
- onUpdated = {},
- )
+ private fun createMediaControl(instanceId: InstanceId): MediaControlViewModel {
+ return kosmos.mediaControlViewModel.copy(instanceId = instanceId)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
index e56b114dc847..fab5a3cdf8fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModelTest.kt
@@ -86,23 +86,23 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
loadMediaControl(KEY_2, instanceId2, isPlaying = true)
loadMediaControl(KEY, instanceId1, isPlaying = false)
- var mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- var mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+ var mediaControl2 = sortedMedia?.get(0) as MediaControlViewModel
+ var mediaControl1 = sortedMedia?.get(1) as MediaControlViewModel
assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
loadMediaControl(KEY, instanceId1, isPlaying = true)
loadMediaControl(KEY_2, instanceId2, isPlaying = false)
- mediaControl2 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- mediaControl1 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+ mediaControl2 = sortedMedia?.get(0) as MediaControlViewModel
+ mediaControl1 = sortedMedia?.get(1) as MediaControlViewModel
assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
underTest.onReorderingAllowed()
- mediaControl1 = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
- mediaControl2 = sortedMedia?.get(1) as MediaCommonViewModel.MediaControl
+ mediaControl1 = sortedMedia?.get(0) as MediaControlViewModel
+ mediaControl2 = sortedMedia?.get(1) as MediaControlViewModel
assertThat(mediaControl1.instanceId).isEqualTo(instanceId1)
assertThat(mediaControl2.instanceId).isEqualTo(instanceId2)
}
@@ -115,7 +115,7 @@ class MediaCarouselViewModelTest : SysuiTestCase() {
loadMediaControl(KEY, instanceId)
- val mediaControl = sortedMedia?.get(0) as MediaCommonViewModel.MediaControl
+ val mediaControl = sortedMedia?.get(0) as MediaControlViewModel
assertThat(mediaControl.instanceId).isEqualTo(instanceId)
// when media control is added to carousel
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index 55e52b780488..18b14a4b3752 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -55,8 +55,6 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() {
@Before
fun setup() {
- whenever(mockContext.getSystemService(eq(WindowManager::class.java)))
- .thenReturn(windowManager)
whenever(mockContext.resources).thenReturn(resources)
}
@@ -154,7 +152,8 @@ class TaskPreviewSizeProviderTest : SysuiTestCase() {
return TaskPreviewSizeProvider(
mockContext,
windowMetricsProvider,
- testConfigurationController
+ testConfigurationController,
+ windowManager
)
.also { it.addCallback(listener) }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
index d1b552906fbb..5d4de02f9aaa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.model
-import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -35,7 +34,7 @@ class SysUiStateExtTest : SysuiTestCase() {
@Test
fun updateFlags() {
- underTest.updateFlags(Display.DEFAULT_DISPLAY, 1L to true, 2L to false, 4L to true)
+ underTest.updateFlags(1L to true, 2L to false, 4L to true)
assertThat(underTest.flags and 1L).isNotEqualTo(0L)
assertThat(underTest.flags and 2L).isEqualTo(0L)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 09e49eb217b0..0e8c5079579e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -82,7 +82,6 @@ import android.view.inputmethod.InputMethodManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.SysuiTestCase;
@@ -218,8 +217,6 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private WindowManager mWindowManager;
@Mock
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock
private TelecomManager mTelecomManager;
@Mock
private InputMethodManager mInputMethodManager;
@@ -685,7 +682,6 @@ public class NavigationBarTest extends SysuiTestCase {
null,
context,
mWindowManager,
- mViewCaptureAwareWindowManager,
() -> mAssistManager,
mock(AccessibilityManager.class),
deviceProvisionedController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
index 52a0a5445002..7fb66ce21919 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModelTest.kt
@@ -22,8 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -77,10 +76,8 @@ class NotificationsShadeOverlayActionsViewModelTest : SysuiTestCase() {
underTest.activateIn(this)
val action =
- (actions?.get(Swipe.Down(fromSource = SceneContainerArea.EndHalf)) as? ShowOverlay)
+ (actions?.get(Swipe.Down(fromSource = SceneContainerArea.TopEdgeEndHalf))
+ as? ReplaceByOverlay)
assertThat(action?.overlay).isEqualTo(Overlays.QuickSettingsShade)
- val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
- assertThat(overlaysToHide).isNotNull()
- assertThat(overlaysToHide?.overlays).containsExactly(Overlays.NotificationsShade)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index 80ce43d61003..ffcd95bc7a4a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -157,7 +157,7 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showMedia_noActiveMedia_false() =
+ fun showMedia_InactiveMedia_false() =
testScope.runTest {
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = false))
runCurrent()
@@ -166,6 +166,16 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
+ fun showMedia_noMedia_false() =
+ testScope.runTest {
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
+ kosmos.mediaFilterRepository.clearSelectedUserMedia()
+ runCurrent()
+
+ assertThat(underTest.showMedia).isFalse()
+ }
+
+ @Test
fun showMedia_qsDisabled_false() =
testScope.runTest {
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java
index 338ed7596bd6..70ded2a7f21c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerUITest.java
@@ -40,11 +40,11 @@ import android.service.vr.IVrStateCallbacks;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
+import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -54,8 +54,6 @@ import com.android.systemui.res.R;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
-import dagger.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -96,7 +94,7 @@ public class PowerUITest extends SysuiTestCase {
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private CommandQueue mCommandQueue;
@Mock private IVrManager mVrManager;
- @Mock private Lazy<ViewCapture> mLazyViewCapture;
+ @Mock private WindowManager mWindowManager;
@Before
public void setup() {
@@ -709,7 +707,7 @@ public class PowerUITest extends SysuiTestCase {
mWakefulnessLifecycle,
mPowerManager,
mUserTracker,
- mLazyViewCapture);
+ mWindowManager);
mPowerUI.mThermalService = mThermalServiceMock;
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index 8b9ae9a0606d..2dd2f7c0f562 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.compose.selection.PlacementEvent
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
@@ -108,6 +109,76 @@ class EditTileListStateTest : SysuiTestCase() {
assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec)
}
+ @Test
+ fun targetIndexForPlacementToTileSpec_returnsCorrectIndex() {
+ val placementEvent =
+ PlacementEvent.PlaceToTileSpec(
+ movingSpec = TestEditTiles[0].tile.tileSpec,
+ targetSpec = TestEditTiles[3].tile.tileSpec,
+ )
+ val index = underTest.targetIndexForPlacement(placementEvent)
+
+ assertThat(index).isEqualTo(3)
+ }
+
+ @Test
+ fun targetIndexForPlacementToIndex_indexOutOfBounds_returnsCorrectIndex() {
+ val placementEventTooLow =
+ PlacementEvent.PlaceToIndex(
+ movingSpec = TestEditTiles[0].tile.tileSpec,
+ targetIndex = -1,
+ )
+ val index1 = underTest.targetIndexForPlacement(placementEventTooLow)
+
+ assertThat(index1).isEqualTo(0)
+
+ val placementEventTooHigh =
+ PlacementEvent.PlaceToIndex(
+ movingSpec = TestEditTiles[0].tile.tileSpec,
+ targetIndex = 10,
+ )
+ val index2 = underTest.targetIndexForPlacement(placementEventTooHigh)
+ assertThat(index2).isEqualTo(TestEditTiles.size)
+ }
+
+ @Test
+ fun targetIndexForPlacementToIndex_movingBack_returnsCorrectIndex() {
+ /**
+ * With the grid: [ a ] [ b ] [ c ] [ Large D ] [ e ] [ f ]
+ *
+ * Moving 'e' to the spacer at index 3 will result in the tilespec order: a, b, c, e, d, f
+ *
+ * 'e' is now at index 3
+ */
+ val placementEvent =
+ PlacementEvent.PlaceToIndex(
+ movingSpec = TestEditTiles[4].tile.tileSpec,
+ targetIndex = 3,
+ )
+ val index = underTest.targetIndexForPlacement(placementEvent)
+
+ assertThat(index).isEqualTo(3)
+ }
+
+ @Test
+ fun targetIndexForPlacementToIndex_movingForward_returnsCorrectIndex() {
+ /**
+ * With the grid: [ a ] [ b ] [ c ] [ Large D ] [ e ] [ f ]
+ *
+ * Moving '1' to the spacer at index 3 will result in the tilespec order: b, c, a, d, e, f
+ *
+ * 'a' is now at index 2
+ */
+ val placementEvent =
+ PlacementEvent.PlaceToIndex(
+ movingSpec = TestEditTiles[0].tile.tileSpec,
+ targetIndex = 3,
+ )
+ val index = underTest.targetIndexForPlacement(placementEvent)
+
+ assertThat(index).isEqualTo(2)
+ }
+
private fun List<GridCell>.toStrings(): List<String> {
return map {
if (it is TileGridCell) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
index ab217a3f50ef..33ee3379c0d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionStateTest.kt
@@ -19,8 +19,14 @@ package com.android.systemui.qs.panels.ui.compose.selection
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.GreyedOut
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.None
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.Placeable
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.Removable
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.Selected
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,7 +51,104 @@ class MutableSelectionStateTest : SysuiTestCase() {
assertThat(underTest.selection).isEqualTo(newSpec)
}
+ @Test
+ fun placementModeEnabled_tapOnIndex_sendsCorrectPlacementEvent() {
+ // Tap while in placement mode
+ underTest.enterPlacementMode(TEST_SPEC)
+ underTest.onTap(2)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ val event = underTest.placementEvent as PlacementEvent.PlaceToIndex
+ assertThat(event.movingSpec).isEqualTo(TEST_SPEC)
+ assertThat(event.targetIndex).isEqualTo(2)
+ }
+
+ @Test
+ fun placementModeDisabled_tapOnIndex_doesNotSendPlacementEvent() {
+ // Tap while placement mode is disabled
+ underTest.onTap(2)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ assertThat(underTest.placementEvent).isNull()
+ }
+
+ @Test
+ fun placementModeEnabled_tapOnSelection_exitPlacementMode() {
+ // Tap while in placement mode
+ underTest.enterPlacementMode(TEST_SPEC)
+ underTest.onTap(TEST_SPEC)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ assertThat(underTest.placementEvent).isNull()
+ }
+
+ @Test
+ fun placementModeEnabled_tapOnTileSpec_sendsCorrectPlacementEvent() {
+ // Tap while in placement mode
+ underTest.enterPlacementMode(TEST_SPEC)
+ underTest.onTap(TEST_SPEC_2)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ val event = underTest.placementEvent as PlacementEvent.PlaceToTileSpec
+ assertThat(event.movingSpec).isEqualTo(TEST_SPEC)
+ assertThat(event.targetSpec).isEqualTo(TEST_SPEC_2)
+ }
+
+ @Test
+ fun placementModeDisabled_tapOnSelection_unselect() {
+ // Select the tile and tap on it
+ underTest.select(TEST_SPEC)
+ underTest.onTap(TEST_SPEC)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ assertThat(underTest.selected).isFalse()
+ }
+
+ @Test
+ fun placementModeDisabled_tapOnTile_selects() {
+ // Select a tile but tap a second one
+ underTest.select(TEST_SPEC)
+ underTest.onTap(TEST_SPEC_2)
+
+ assertThat(underTest.placementEnabled).isFalse()
+ assertThat(underTest.selection).isEqualTo(TEST_SPEC_2)
+ }
+
+ @Test
+ fun tileStateFor_selectedTile_returnsSingleSelection() = runTest {
+ underTest.select(TEST_SPEC)
+
+ assertThat(underTest.tileStateFor(TEST_SPEC, None, canShowRemovalBadge = true))
+ .isEqualTo(Selected)
+ assertThat(underTest.tileStateFor(TEST_SPEC_2, None, canShowRemovalBadge = true))
+ .isEqualTo(Removable)
+ assertThat(underTest.tileStateFor(TEST_SPEC_3, None, canShowRemovalBadge = true))
+ .isEqualTo(Removable)
+ }
+
+ @Test
+ fun tileStateFor_placementMode_returnsSinglePlaceable() = runTest {
+ underTest.enterPlacementMode(TEST_SPEC)
+
+ assertThat(underTest.tileStateFor(TEST_SPEC, None, canShowRemovalBadge = true))
+ .isEqualTo(Placeable)
+ assertThat(underTest.tileStateFor(TEST_SPEC_2, None, canShowRemovalBadge = true))
+ .isEqualTo(GreyedOut)
+ assertThat(underTest.tileStateFor(TEST_SPEC_3, None, canShowRemovalBadge = true))
+ .isEqualTo(GreyedOut)
+ }
+
+ @Test
+ fun tileStateFor_nonRemovableTile_returnsNoneState() = runTest {
+ assertThat(underTest.tileStateFor(TEST_SPEC, None, canShowRemovalBadge = true))
+ .isEqualTo(Removable)
+ assertThat(underTest.tileStateFor(TEST_SPEC_2, None, canShowRemovalBadge = false))
+ .isEqualTo(None)
+ }
+
companion object {
private val TEST_SPEC = TileSpec.create("testSpec")
+ private val TEST_SPEC_2 = TileSpec.create("testSpec2")
+ private val TEST_SPEC_3 = TileSpec.create("testSpec3")
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt
new file mode 100644
index 000000000000..468c3dc3be93
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/dialog/CastDetailsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.tiles.dialog
+
+import android.content.Context
+import android.media.MediaRouter
+import android.provider.Settings
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.app.MediaRouteDialogPresenter
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.tiles.base.domain.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.domain.actions.intentInputs
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidJUnit4::class)
+class CastDetailsViewModelTest : SysuiTestCase() {
+ var inputHandler: FakeQSTileIntentUserInputHandler = FakeQSTileIntentUserInputHandler()
+ private var context: Context = mock()
+ private var mediaRouter: MediaRouter = mock()
+ private var selectedRoute: MediaRouter.RouteInfo = mock()
+
+ @Test
+ fun testClickOnSettingsButton() {
+ var viewModel = CastDetailsViewModel(inputHandler, context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)
+
+ viewModel.clickOnSettingsButton()
+
+ assertThat(inputHandler.handledInputs).hasSize(1)
+ val intentInput = inputHandler.intentInputs.last()
+ assertThat(intentInput.expandable).isNull()
+ assertThat(intentInput.intent.action).isEqualTo(Settings.ACTION_CAST_SETTINGS)
+ }
+
+ @Test
+ fun testShouldShowChooserDialog() {
+ context.stub {
+ on { getSystemService(MediaRouter::class.java) } doReturn mediaRouter
+ }
+ mediaRouter.stub {
+ on { selectedRoute } doReturn selectedRoute
+ }
+
+ var viewModel =
+ CastDetailsViewModel(inputHandler, context, MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)
+
+ assertThat(viewModel.shouldShowChooserDialog())
+ .isEqualTo(
+ MediaRouteDialogPresenter.shouldShowChooserDialog(
+ context,
+ MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY,
+ )
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
index 701e55d0759d..d11701570e32 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModelTest.kt
@@ -95,11 +95,21 @@ class QuickSettingsContainerViewModelTest : SysuiTestCase() {
}
@Test
- fun showMedia_noActiveMedia_false() =
+ fun showMedia_InactiveMedia_true() =
testScope.runTest {
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = false))
runCurrent()
+ assertThat(underTest.showMedia).isTrue()
+ }
+
+ @Test
+ fun showMedia_noMedia_false() =
+ testScope.runTest {
+ kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
+ kosmos.mediaFilterRepository.clearSelectedUserMedia()
+ runCurrent()
+
assertThat(underTest.showMedia).isFalse()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
index b98059a1fe90..882d296d94b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModelTest.kt
@@ -22,8 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.EnableSceneContainer
@@ -90,11 +89,8 @@ class QuickSettingsShadeOverlayActionsViewModelTest : SysuiTestCase() {
underTest.activateIn(this)
val action =
- (actions?.get(Swipe.Down(fromSource = SceneContainerArea.StartHalf))
- as? ShowOverlay)
+ (actions?.get(Swipe.Down(fromSource = SceneContainerArea.TopEdgeStartHalf))
+ as? ReplaceByOverlay)
assertThat(action?.overlay).isEqualTo(Overlays.NotificationsShade)
- val overlaysToHide = action?.hideCurrentOverlays as? HideCurrentOverlays.Some
- assertThat(overlaysToHide).isNotNull()
- assertThat(overlaysToHide?.overlays).containsExactly(Overlays.QuickSettingsShade)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 1743e056b65c..6d4fffdefb1b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -22,7 +22,6 @@ import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
-import android.view.Display
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -1213,15 +1212,15 @@ class SceneContainerStartableTest : SysuiTestCase() {
fakeSceneDataSource.pause()
sceneInteractor.changeScene(sceneKey, "reason")
runCurrent()
- verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+ verify(sysUiState, times(index)).commitUpdate()
fakeSceneDataSource.unpause(expectedScene = sceneKey)
runCurrent()
- verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+ verify(sysUiState, times(index)).commitUpdate()
transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
runCurrent()
- verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
+ verify(sysUiState, times(index + 1)).commitUpdate()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt
index a09e5cd9de9b..7536bb3cc02a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetectorTest.kt
@@ -32,6 +32,8 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.LeftE
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.LeftHalf
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.RightEdge
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.RightHalf
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.TopEdgeLeftHalf
+import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.Resolved.TopEdgeRightHalf
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.StartEdge
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea.StartHalf
import com.google.common.truth.Truth.assertThat
@@ -55,9 +57,9 @@ class SceneContainerSwipeDetectorTest : SysuiTestCase() {
}
@Test
- fun source_swipeVerticallyOnTopLeft_detectsLeftHalf() {
+ fun source_swipeVerticallyOnTopLeft_detectsTopEdgeLeftHalf() {
val detectedEdge = swipeVerticallyFrom(x = 1, y = edgeSize - 1)
- assertThat(detectedEdge).isEqualTo(LeftHalf)
+ assertThat(detectedEdge).isEqualTo(TopEdgeLeftHalf)
}
@Test
@@ -69,7 +71,7 @@ class SceneContainerSwipeDetectorTest : SysuiTestCase() {
@Test
fun source_swipeVerticallyOnTopRight_detectsRightHalf() {
val detectedEdge = swipeVerticallyFrom(x = screenWidth - 1, y = edgeSize - 1)
- assertThat(detectedEdge).isEqualTo(RightHalf)
+ assertThat(detectedEdge).isEqualTo(TopEdgeRightHalf)
}
@Test
@@ -79,14 +81,26 @@ class SceneContainerSwipeDetectorTest : SysuiTestCase() {
}
@Test
- fun source_swipeVerticallyToLeftOfSplit_detectsLeftHalf() {
+ fun source_swipeVerticallyOnTopEdge_ToLeftOfSplit_detectsTopEdgeLeftHalf() {
val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) - 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(TopEdgeLeftHalf)
+ }
+
+ @Test
+ fun source_swipeVerticallyBelowTopEdge_ToLeftOfSplit_detectsLeftHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) - 1, y = edgeSize + 1)
assertThat(detectedEdge).isEqualTo(LeftHalf)
}
@Test
- fun source_swipeVerticallyToRightOfSplit_detectsRightHalf() {
+ fun source_swipeVerticallyOnTopEdge_toRightOfSplit_detectsTopEdgeRightHalf() {
val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) + 1, y = edgeSize - 1)
+ assertThat(detectedEdge).isEqualTo(TopEdgeRightHalf)
+ }
+
+ @Test
+ fun source_swipeVerticallyBelowTopEdge_toRightOfSplit_detectsRightHalf() {
+ val detectedEdge = swipeVerticallyFrom(x = (screenWidth / 2) + 1, y = edgeSize + 1)
assertThat(detectedEdge).isEqualTo(RightHalf)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 94db429c2225..676e1ea5321a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -172,6 +172,7 @@ import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.android.systemui.wallpapers.ui.viewmodel.WallpaperFocalAreaViewModel;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -296,6 +297,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
@Mock private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
@Mock protected SysUIStateDisplaysInteractor mSysUIStateDisplaysInteractor;
+ @Mock private WindowManagerProvider mWindowManagerProvider;
protected final int mMaxUdfpsBurnInOffsetY = 5;
protected FakeFeatureFlagsClassic mFeatureFlags = new FakeFeatureFlagsClassic();
protected KeyguardClockInteractor mKeyguardClockInteractor;
@@ -672,7 +674,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mCastController,
new ResourcesSplitShadeStateController(),
() -> mKosmos.getCommunalTransitionViewModel(),
- () -> mLargeScreenHeaderHelper
+ () -> mLargeScreenHeaderHelper,
+ mWindowManagerProvider
);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 3788049256a2..fd17b7a32fd3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -50,7 +50,6 @@ import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
@@ -100,7 +99,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Rule public final CheckFlagsRule checkFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
- @Mock private ViewCaptureAwareWindowManager mWindowManager;
+ @Mock private WindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
@Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
new NotificationShadeWindowView(mContext, null));
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 7433267ab3b0..db0c07c50dc6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.Looper;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
@@ -79,6 +80,7 @@ import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupReposi
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import dagger.Lazy;
@@ -147,6 +149,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
@Mock protected UserSwitcherInteractor mUserSwitcherInteractor;
@Mock protected SelectedUserInteractor mSelectedUserInteractor;
@Mock protected LargeScreenHeaderHelper mLargeScreenHeaderHelper;
+ @Mock protected WindowManagerProvider mWindowManagerProvider;
protected FakeDisableFlagsRepository mDisableFlagsRepository =
mKosmos.getFakeDisableFlagsRepository();
@@ -232,6 +235,9 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mMainHandler = new Handler(Looper.getMainLooper());
+ WindowManager wm = mContext.getSystemService(WindowManager.class);
+ when(mWindowManagerProvider.getWindowManager(mContext)).thenReturn(wm);
+
mQsController = new QuickSettingsControllerImpl(
mPanelViewControllerLazy,
mPanelView,
@@ -268,7 +274,8 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mCastController,
splitShadeStateController,
() -> mKosmos.getCommunalTransitionViewModel(),
- () -> mLargeScreenHeaderHelper
+ () -> mLargeScreenHeaderHelper,
+ mWindowManagerProvider
);
mQsController.init();
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index 32eec56af8ea..b376558371f3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -21,6 +21,7 @@ import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
@@ -30,8 +31,10 @@ import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticati
import com.android.systemui.kosmos.testCase
import com.android.systemui.kosmos.testScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.testKosmos
@@ -177,6 +180,40 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
verify(testRunnable, times(1)).run()
}
+ @Test
+ fun instantCollapseShade_notificationShadeHidden() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ // GIVEN the dual shade configuration with the notification shade overlay visible
+ kosmos.enableDualShade()
+ runCurrent()
+ sceneInteractor.showOverlay(Overlays.NotificationsShade, "test")
+ assertThat(currentOverlays).isEqualTo(setOf(Overlays.NotificationsShade))
+
+ // WHEN shade instantly collapses
+ underTest.instantCollapseShade()
+
+ // THEN overlay was hidden
+ assertThat(currentOverlays).isEmpty()
+ }
+
+ @Test
+ fun instantCollapseShade_qsShadeHidden() =
+ testScope.runTest {
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ // GIVEN the dual shade configuration with the QS shade overlay visible
+ kosmos.enableDualShade()
+ runCurrent()
+ sceneInteractor.showOverlay(Overlays.QuickSettingsShade, "test")
+ assertThat(currentOverlays).isEqualTo(setOf(Overlays.QuickSettingsShade))
+
+ // WHEN shade instantly collapses
+ underTest.instantCollapseShade()
+
+ // THEN overlay was hidden
+ assertThat(currentOverlays).isEmpty()
+ }
+
private fun setScene(key: SceneKey) {
sceneInteractor.changeScene(key, "test")
sceneInteractor.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
index 70df82d95008..c26f18f5ab6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -36,7 +36,9 @@ import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
import android.os.Bundle;
+import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
+import android.util.Pair;
import android.view.KeyEvent;
import android.view.WindowInsets;
import android.view.WindowInsets.Type.InsetsType;
@@ -46,6 +48,7 @@ import android.view.WindowInsetsController.Behavior;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.internal.statusbar.DisableStates;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
@@ -58,11 +61,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.HashMap;
+import java.util.Map;
+
@SmallTest
@RunWith(AndroidJUnit4.class)
public class CommandQueueTest extends SysuiTestCase {
- private static final LetterboxDetails[] TEST_LETTERBOX_DETAILS = new LetterboxDetails[] {
+ private static final LetterboxDetails[] TEST_LETTERBOX_DETAILS = new LetterboxDetails[]{
new LetterboxDetails(
/* letterboxInnerBounds= */ new Rect(100, 0, 200, 500),
/* letterboxFullBounds= */ new Rect(0, 0, 500, 100),
@@ -119,6 +125,27 @@ public class CommandQueueTest extends SysuiTestCase {
}
@Test
+ public void testDisableForAllDisplays() throws RemoteException {
+ int state1 = 14;
+ int state2 = 42;
+ int secondaryDisplayState1 = 16;
+ int secondaryDisplayState2 = 44;
+ Map<Integer, Pair<Integer, Integer>> displaysWithStates = new HashMap<>();
+ displaysWithStates.put(DEFAULT_DISPLAY, new Pair<>(state1, state2)); // Example values
+ displaysWithStates.put(SECONDARY_DISPLAY,
+ new Pair<>(secondaryDisplayState1, secondaryDisplayState2)); // Example values
+ DisableStates expectedDisableStates = new DisableStates(displaysWithStates, true);
+
+ mCommandQueue.disableForAllDisplays(expectedDisableStates);
+ waitForIdleSync();
+
+ verify(mCallbacks).disable(eq(DEFAULT_DISPLAY), eq(state1), eq(state2), eq(true));
+ verify(mCallbacks).disable(eq(SECONDARY_DISPLAY), eq(secondaryDisplayState1),
+ eq(secondaryDisplayState2), eq(true));
+ }
+
+
+ @Test
public void testExpandNotifications() {
mCommandQueue.animateExpandNotificationsPanel();
waitForIdleSync();
@@ -475,7 +502,8 @@ public class CommandQueueTest extends SysuiTestCase {
final long requestId = 10;
mCommandQueue.showAuthenticationDialog(promptInfo, receiver, sensorIds,
- credentialAllowed, requireConfirmation, userId, operationId, packageName, requestId);
+ credentialAllowed, requireConfirmation, userId, operationId, packageName,
+ requestId);
waitForIdleSync();
verify(mCallbacks).showAuthenticationDialog(eq(promptInfo), eq(receiver), eq(sensorIds),
eq(credentialAllowed), eq(requireConfirmation), eq(userId), eq(operationId),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 3ecf302204bc..67af7a54988e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.DozeParameters
@@ -45,6 +46,8 @@ import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -65,8 +68,6 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.Optional
-import java.util.function.Consumer
@RunWith(AndroidJUnit4::class)
@RunWithLooper
@@ -75,6 +76,7 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val applicationScope = kosmos.testScope.backgroundScope
+ private val shadeDisplayRepository = kosmos.fakeShadeDisplaysRepository
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var blurUtils: BlurUtils
@Mock private lateinit var biometricUnlockController: BiometricUnlockController
@@ -135,7 +137,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
windowRootViewBlurInteractor,
appZoomOutOptional,
applicationScope,
- dumpManager
+ dumpManager,
+ { shadeDisplayRepository },
)
notificationShadeDepthController.shadeAnimation = shadeAnimation
notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
@@ -355,6 +358,36 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInExternalDisplay_doesSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(1) // not default display.
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(eq(0f))
+ verify(listener).onWallpaperZoomOutChanged(eq(0f))
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_SHADE_WINDOW_GOES_AROUND)
+ fun updateBlurCallback_shadeInDefaultDisplay_doesNotSetZeroZoom() {
+ notificationShadeDepthController.onPanelExpansionChanged(
+ ShadeExpansionChangeEvent(fraction = 1f, expanded = true, tracking = false)
+ )
+ notificationShadeDepthController.addListener(listener)
+ shadeDisplayRepository.setDisplayId(0) // shade is in default display
+
+ notificationShadeDepthController.updateBlurCallback.doFrame(0)
+
+ verify(wallpaperController).setNotificationShadeZoom(floatThat { it != 0f })
+ verify(listener).onWallpaperZoomOutChanged(floatThat { it != 0f })
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_NOTIFICATION_SHADE_BLUR)
fun updateBlurCallback_setsOpaque_whenScrim() {
scrimVisibilityCaptor.value.accept(ScrimController.OPAQUE)
@@ -488,10 +521,10 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
}
private fun enableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(true)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(true)
}
private fun disableSplitShade() {
- `when` (shadeModeInteractor.isSplitShade).thenReturn(false)
+ `when`(shadeModeInteractor.isSplitShade).thenReturn(false)
}
}
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 6c498c8fd2f1..5ec9b601bcca 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
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarRootModernization
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
@@ -799,8 +800,8 @@ class CallChipViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
private val PROMOTED_CONTENT_WITH_COLOR =
- PromotedNotificationContentModel.Builder("notif")
- .apply {
+ PromotedNotificationContentBuilder("notif")
+ .applyToShared {
this.colors =
PromotedNotificationContentModel.Colors(
backgroundColor = PROMOTED_BACKGROUND_COLOR,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index 7f8f5f43e775..9ce43a0792c2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -30,7 +30,7 @@ import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -420,7 +420,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
// WHEN the notif gets a new UID that starts as visible
activityManagerRepository.fake.startingIsAppVisibleValue = true
val newPromotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Arrived"
}
val newPromotedContent = newPromotedContentBuilder.build()
@@ -452,6 +452,6 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
companion object {
private const val UID = 885
- private val PROMOTED_CONTENT = PromotedNotificationContentModel.Builder("notif1").build()
+ private val PROMOTED_CONTENT = PromotedNotificationContentBuilder("notif1").build()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index 0b9b297130a2..233efbb97d4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -36,7 +36,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
@@ -56,16 +56,16 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_flagOff_noNotifs() =
+ fun allNotificationChips_flagOff_noNotifs() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -75,9 +75,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_noNotifs_empty() =
+ fun allNotificationChips_noNotifs_empty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(emptyList())
@@ -87,16 +87,16 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
+ fun allNotificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -106,16 +106,16 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
- fun shownNotificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
+ fun allNotificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -125,9 +125,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_onePromotedNotif_statusBarIconViewMatches() =
+ fun allNotificationChips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val icon = mock<StatusBarIconView>()
setNotifs(
@@ -135,7 +135,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -147,9 +147,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_onlyForPromotedNotifs() =
+ fun allNotificationChips_onlyForPromotedNotifs() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -158,12 +158,12 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
@@ -182,11 +182,11 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_onlyForNotVisibleApps() =
+ fun allNotificationChips_appVisibilityInfoCorrect() =
kosmos.runTest {
activityManagerRepository.fake.startingIsAppVisibleValue = false
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val uid = 433
setNotifs(
@@ -195,27 +195,30 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = "notif",
uid = uid,
statusBarChipIcon = mock<StatusBarIconView>(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isAppVisible).isFalse()
activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = true)
- assertThat(latest).isEmpty()
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isAppVisible).isTrue()
activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isAppVisible).isFalse()
}
/** Regression test for b/388521980. */
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
+ fun allNotificationChips_callNotifIsAlsoPromoted_callNotifExcluded() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(
listOf(
@@ -223,14 +226,14 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = "promotedNormal",
statusBarChipIcon = mock(),
promotedContent =
- PromotedNotificationContentModel.Builder("promotedNormal").build(),
+ PromotedNotificationContentBuilder("promotedNormal").build(),
callType = CallType.None,
),
activeNotificationModel(
key = "promotedCall",
statusBarChipIcon = mock(),
promotedContent =
- PromotedNotificationContentModel.Builder("promotedCall").build(),
+ PromotedNotificationContentBuilder("promotedCall").build(),
callType = CallType.Ongoing,
),
)
@@ -243,9 +246,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_notifUpdatesGoThrough() =
+ fun allNotificationChips_notifUpdatesGoThrough() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -256,7 +259,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -269,7 +272,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -282,7 +285,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = thirdIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -293,16 +296,16 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_promotedNotifDisappearsThenReappears() =
+ fun allNotificationChips_promotedNotifDisappearsThenReappears() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -325,7 +328,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -335,9 +338,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_sortedByFirstAppearanceTime() =
+ fun allNotificationChips_sortedByFirstAppearanceTime() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -348,7 +351,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
setNotifs(listOf(notif1))
assertThat(latest!!.map { it.key }).containsExactly("notif1").inOrder()
@@ -359,7 +362,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
setNotifs(listOf(notif1, notif2))
@@ -380,7 +383,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
// WHEN notif1 gets an update
val notif1NewPromotedContent =
- PromotedNotificationContentModel.Builder("notif1").apply {
+ PromotedNotificationContentBuilder("notif1").applyToShared {
this.shortCriticalText = "Arrived"
}
setNotifs(
@@ -411,9 +414,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_lastAppVisibleTimeMaintainedAcrossNotifAddsAndRemoves() =
+ fun allNotificationChips_lastAppVisibleTimeMaintainedAcrossNotifAddsAndRemoves() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
@@ -426,8 +429,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
)
activityManagerRepository.fake.setIsAppVisible(notif1Info.uid, isAppVisible = false)
@@ -443,8 +445,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
)
activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
@@ -468,9 +469,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_sortedByLastAppVisibleTime() =
+ fun allNotificationChips_sortedByLastAppVisibleTime() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
@@ -482,16 +483,14 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
val notif2 =
activeNotificationModel(
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
@@ -500,15 +499,19 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
fakeSystemClock.advanceTime(1000)
activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = true)
- // THEN notif2 is no longer shown
- assertThat(latest!!.map { it.key }).containsExactly("notif1").inOrder()
+ // THEN notif2 is ranked above notif1 because it was more recently visible
+ assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+ assertThat(latest!![0].isAppVisible).isTrue() // notif2
+ assertThat(latest!![1].isAppVisible).isFalse() // notif1
// WHEN notif2's app is no longer visible
fakeSystemClock.advanceTime(1000)
activityManagerRepository.fake.setIsAppVisible(notif2Info.uid, isAppVisible = false)
- // THEN notif2 is ranked above notif1 because it was more recently visible
+ // THEN notif2 is still ranked above notif1
assertThat(latest!!.map { it.key }).containsExactly("notif2", "notif1").inOrder()
+ assertThat(latest!![0].isAppVisible).isFalse() // notif2
+ assertThat(latest!![1].isAppVisible).isFalse() // notif1
// WHEN the app associated with notif1 becomes visible then un-visible
fakeSystemClock.advanceTime(1000)
@@ -522,9 +525,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_newNotificationTakesPriorityOverLastAppVisible() =
+ fun allNotificationChips_newNotificationTakesPriorityOverLastAppVisible() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
@@ -537,16 +540,14 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
val notif2 =
activeNotificationModel(
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
assertThat(latest!!.map { it.key }).containsExactly("notif1", "notif2").inOrder()
@@ -567,8 +568,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif3Info.key,
uid = notif3Info.uid,
statusBarChipIcon = notif3Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif3Info.key).build(),
)
setNotifs(listOf(notif1, notif2, notif3))
@@ -581,9 +581,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_fullSort() =
+ fun allNotificationChips_fullSort() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val notif1Info = NotifInfo("notif1", mock<StatusBarIconView>(), uid = 100)
val notif2Info = NotifInfo("notif2", mock<StatusBarIconView>(), uid = 200)
@@ -597,8 +597,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif1Info.key,
uid = notif1Info.uid,
statusBarChipIcon = notif1Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif1Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif1Info.key).build(),
)
setNotifs(listOf(notif1))
@@ -609,8 +608,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif2Info.key,
uid = notif2Info.uid,
statusBarChipIcon = notif2Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif2Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif2Info.key).build(),
)
setNotifs(listOf(notif1, notif2))
@@ -637,7 +635,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
// WHEN notif2 gets an update
val notif2NewPromotedContent =
- PromotedNotificationContentModel.Builder("notif2").apply {
+ PromotedNotificationContentBuilder("notif2").applyToShared {
this.shortCriticalText = "Arrived"
}
setNotifs(
@@ -662,8 +660,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
key = notif3Info.key,
uid = notif3Info.uid,
statusBarChipIcon = notif3Info.icon,
- promotedContent =
- PromotedNotificationContentModel.Builder(notif3Info.key).build(),
+ promotedContent = PromotedNotificationContentBuilder(notif3Info.key).build(),
)
setNotifs(listOf(notif1, notif2, notif3))
@@ -699,9 +696,9 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shownNotificationChips_notifChangesKey() =
+ fun allNotificationChips_notifChangesKey() =
kosmos.runTest {
- val latest by collectLastValue(underTest.shownNotificationChips)
+ val latest by collectLastValue(underTest.allNotificationChips)
val firstIcon = mock<StatusBarIconView>()
val secondIcon = mock<StatusBarIconView>()
@@ -710,8 +707,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif|uid1",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("notif|uid1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif|uid1").build(),
)
)
)
@@ -725,8 +721,7 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif|uid2",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("notif|uid2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif|uid2").build(),
)
)
)
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 8368fa671ea8..660f0b52f464 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
@@ -24,6 +24,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY
import com.android.systemui.SysuiTestCase
+import com.android.systemui.activity.data.repository.activityManagerRepository
+import com.android.systemui.activity.data.repository.fake
import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -43,6 +45,7 @@ import com.android.systemui.statusbar.notification.data.repository.ActiveNotific
import com.android.systemui.statusbar.notification.data.repository.UnconfinedFakeHeadsUpRowRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -101,7 +104,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -121,7 +124,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -142,7 +145,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
key = "notif",
appName = "Fake App Name",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -172,7 +175,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
key = notifKey,
appName = "Fake App Name",
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(notifKey).build(),
)
)
)
@@ -195,7 +198,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chips)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.colors =
PromotedNotificationContentModel.Colors(
backgroundColor = 56,
@@ -229,12 +232,12 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = firstIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
),
activeNotificationModel(
key = "notif2",
statusBarChipIcon = secondIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
),
activeNotificationModel(
key = "notif3",
@@ -264,13 +267,12 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = firstKey,
statusBarChipIcon = null,
- promotedContent = PromotedNotificationContentModel.Builder(firstKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(firstKey).build(),
),
activeNotificationModel(
key = secondKey,
statusBarChipIcon = null,
- promotedContent =
- PromotedNotificationContentModel.Builder(secondKey).build(),
+ promotedContent = PromotedNotificationContentBuilder(secondKey).build(),
),
activeNotificationModel(
key = thirdKey,
@@ -286,6 +288,84 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ fun chips_appStartsAsVisible_isHiddenTrue() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+
+ val latest by collectLastValue(underTest.chips)
+
+ val uid = 433
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = uid,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isHidden).isTrue()
+ }
+
+ @Test
+ fun chips_appStartsAsNotVisible_isHiddenFalse() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val latest by collectLastValue(underTest.chips)
+
+ val uid = 433
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = uid,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isHidden).isFalse()
+ }
+
+ @Test
+ fun chips_isHidden_changesBasedOnAppVisibility() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val latest by collectLastValue(underTest.chips)
+
+ val uid = 433
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = uid,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
+ )
+ )
+ )
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isHidden).isFalse()
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = true)
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isHidden).isTrue()
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0].isHidden).isFalse()
+ }
+
+ @Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_hasShortCriticalText_usesTextInsteadOfTime() =
kosmos.runTest {
@@ -294,7 +374,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Arrived"
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -321,7 +401,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chips)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply { this.time = null }
+ PromotedNotificationContentBuilder("notif").applyToShared { this.time = null }
setNotifs(
listOf(
activeNotificationModel(
@@ -346,7 +426,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.wasPromotedAutomatically = true
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -374,7 +454,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.wasPromotedAutomatically = false
this.time = When.Time(currentTime + 30.minutes.inWholeMilliseconds)
}
@@ -402,7 +482,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 13.minutes.inWholeMilliseconds)
}
@@ -422,6 +502,42 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
+ fun chips_basicTime_respectsIsAppVisible() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = false
+
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 3.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val promotedContentBuilder =
+ PromotedNotificationContentBuilder("notif").applyToShared {
+ this.time = When.Time(currentTime + 13.minutes.inWholeMilliseconds)
+ }
+ val uid = 3
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = 3,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0])
+ .isInstanceOf(OngoingActivityChipModel.Active.ShortTimeDelta::class.java)
+ assertThat(latest!![0].isHidden).isFalse()
+
+ activityManagerRepository.fake.setIsAppVisible(uid = uid, isAppVisible = true)
+
+ assertThat(latest!![0].isHidden).isTrue()
+ }
+
+ @Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_basicTime_timeLessThanOneMinInFuture_isIconOnly() =
kosmos.runTest {
@@ -430,7 +546,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 500)
}
@@ -458,7 +574,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime)
}
@@ -486,7 +602,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime - 2.minutes.inWholeMilliseconds)
}
@@ -515,7 +631,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 3.minutes.inWholeMilliseconds)
}
@@ -555,7 +671,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
val whenElapsed = currentElapsed - 1.minutes.inWholeMilliseconds
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time =
When.Chronometer(elapsedRealtimeMillis = whenElapsed, isCountDown = false)
}
@@ -579,6 +695,48 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ fun chips_countUpTime_respectsIsAppVisible() =
+ kosmos.runTest {
+ activityManagerRepository.fake.startingIsAppVisibleValue = true
+
+ val latest by collectLastValue(underTest.chips)
+ val currentTime = 30.minutes.inWholeMilliseconds
+ fakeSystemClock.setCurrentTimeMillis(currentTime)
+
+ val currentElapsed =
+ currentTime + fakeSystemClock.elapsedRealtime() -
+ fakeSystemClock.currentTimeMillis()
+
+ val whenElapsed = currentElapsed - 1.minutes.inWholeMilliseconds
+
+ val promotedContentBuilder =
+ PromotedNotificationContentBuilder("notif").applyToShared {
+ this.time =
+ When.Chronometer(elapsedRealtimeMillis = whenElapsed, isCountDown = false)
+ }
+ val uid = 6
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ uid = uid,
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
+ promotedContent = promotedContentBuilder.build(),
+ )
+ )
+ )
+
+ assertThat(latest).hasSize(1)
+ assertThat(latest!![0]).isInstanceOf(OngoingActivityChipModel.Active.Timer::class.java)
+ assertThat(latest!![0].isHidden).isTrue()
+
+ activityManagerRepository.fake.setIsAppVisible(uid, isAppVisible = false)
+
+ assertThat(latest!![0].isHidden).isFalse()
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_countDownTime_isTimer() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -592,7 +750,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
val whenElapsed = currentElapsed + 10.minutes.inWholeMilliseconds
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time =
When.Chronometer(elapsedRealtimeMillis = whenElapsed, isCountDown = true)
}
@@ -623,7 +781,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -653,7 +811,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -690,11 +848,11 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
val otherPromotedContentBuilder =
- PromotedNotificationContentModel.Builder("other notif").apply {
+ PromotedNotificationContentBuilder("other notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
val icon = createStatusBarIconViewOrNull()
@@ -738,7 +896,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
fakeSystemClock.setCurrentTimeMillis(currentTime)
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.time = When.Time(currentTime + 10.minutes.inWholeMilliseconds)
}
setNotifs(
@@ -781,7 +939,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key,
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder(key).build(),
+ promotedContent = PromotedNotificationContentBuilder(key).build(),
)
)
)
@@ -809,7 +967,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key,
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder(key).build(),
+ promotedContent = PromotedNotificationContentBuilder(key).build(),
)
)
)
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 608a84bdb604..83b3c9cae3c1 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
@@ -63,7 +63,7 @@ import com.android.systemui.statusbar.notification.data.repository.activeNotific
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.addNotifs
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.ongoingcall.DisableChipsModernization
@@ -358,7 +358,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
addOngoingCallState(key = "call")
val promotedContentBuilder =
- PromotedNotificationContentModel.Builder("notif").apply {
+ PromotedNotificationContentBuilder("notif").applyToShared {
this.shortCriticalText = "Some text here"
}
activeNotificationListRepository.addNotif(
@@ -741,7 +741,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -765,7 +765,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -791,14 +791,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -822,14 +820,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -857,20 +853,17 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -896,26 +889,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
activeNotificationModel(
key = "fourthNotif",
statusBarChipIcon = fourthIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("fourthNotif").build(),
),
)
)
@@ -941,20 +930,17 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -973,26 +959,22 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
activeNotificationModel(
key = "fourthNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("fourthNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("fourthNotif").build(),
),
)
)
@@ -1016,14 +998,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
)
)
@@ -1050,20 +1030,17 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "firstNotif",
statusBarChipIcon = firstIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("firstNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("firstNotif").build(),
),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = secondIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("secondNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("secondNotif").build(),
),
activeNotificationModel(
key = "thirdNotif",
statusBarChipIcon = thirdIcon,
- promotedContent =
- PromotedNotificationContentModel.Builder("thirdNotif").build(),
+ promotedContent = PromotedNotificationContentBuilder("thirdNotif").build(),
),
)
)
@@ -1092,7 +1069,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
@@ -1114,14 +1091,14 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1143,14 +1120,14 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1178,7 +1155,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
addOngoingCallState(key = callNotificationKey)
@@ -1189,7 +1166,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif2",
statusBarChipIcon = notifIcon2,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -1214,7 +1191,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
)
@@ -1265,7 +1242,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
@@ -1304,7 +1281,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = notifIcon,
- promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif").build(),
)
)
// And everything else hidden
@@ -1382,7 +1359,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = notif1Icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
)
@@ -1436,7 +1413,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif2",
statusBarChipIcon = notif2Icon,
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index d3befa921e9e..29bb29f25797 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -29,7 +29,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -170,7 +170,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
val promoted1 =
activeNotificationModel(
key = "notif1",
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
@@ -208,14 +208,14 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
val promoted1 =
activeNotificationModel(
key = "notif1",
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
val notPromoted2 = activeNotificationModel(key = "notif2", promotedContent = null)
val notPromoted3 = activeNotificationModel(key = "notif3", promotedContent = null)
val promoted4 =
activeNotificationModel(
key = "notif4",
- promotedContent = PromotedNotificationContentModel.Builder("notif4").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif4").build(),
)
activeNotificationListRepository.activeNotifications.value =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 35b19c19d5ce..873357c2201e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -21,20 +21,21 @@ import android.service.notification.StatusBarNotification
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.byKey
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -42,17 +43,14 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class RenderNotificationsListInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
- private val notifsRepository = kosmos.activeNotificationListRepository
- private val notifsInteractor = kosmos.activeNotificationsInteractor
- private val underTest =
- RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock(), context)
+ private val Kosmos.outputInteractor by Kosmos.Fixture { activeNotificationsInteractor }
+ private val Kosmos.underTest by Kosmos.Fixture { renderNotificationListInteractor }
@Test
fun setRenderedList_preservesOrdering() =
- testScope.runTest {
- val notifs by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+ kosmos.runTest {
+ val notifs by collectLastValue(outputInteractor.topLevelRepresentativeNotifications)
val keys = (1..50).shuffled().map { "$it" }
val entries = keys.map { mockNotificationEntry(key = it) }
underTest.setRenderedList(entries)
@@ -64,8 +62,8 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
@Test
fun setRenderList_flatMapsRankings() =
- testScope.runTest {
- val ranks by collectLastValue(notifsInteractor.activeNotificationRanks)
+ kosmos.runTest {
+ val ranks by collectLastValue(outputInteractor.activeNotificationRanks)
val single = mockNotificationEntry("single", 0)
val group =
@@ -89,8 +87,8 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
@Test
fun setRenderList_singleItems_mapsRankings() =
- testScope.runTest {
- val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+ kosmos.runTest {
+ val actual by collectLastValue(outputInteractor.activeNotificationRanks)
val expected =
(0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
@@ -103,8 +101,8 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
@Test
fun setRenderList_groupWithNoSummary_flatMapsRankings() =
- testScope.runTest {
- val actual by collectLastValue(notifsInteractor.activeNotificationRanks)
+ kosmos.runTest {
+ val actual by collectLastValue(outputInteractor.activeNotificationRanks)
val expected =
(0..10).shuffled().mapIndexed { index, value -> "$value" to index }.toMap()
@@ -123,14 +121,14 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
fun setRenderList_setsPromotionContent() =
- testScope.runTest {
- val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+ kosmos.runTest {
+ val actual by collectLastValue(outputInteractor.topLevelRepresentativeNotifications)
val notPromoted1 = mockNotificationEntry("key1", promotedContent = null)
val promoted2 =
mockNotificationEntry(
"key2",
- promotedContent = PromotedNotificationContentModel.Builder("key2").build(),
+ promotedContent = PromotedNotificationContentBuilder("key2").build(),
)
underTest.setRenderedList(listOf(notPromoted1, promoted2))
@@ -149,7 +147,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
private fun mockNotificationEntry(
key: String,
rank: Int = 0,
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
): NotificationEntry {
val nBuilder = Notification.Builder(context, "a")
val notification = nBuilder.build()
@@ -165,7 +163,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
whenever(this.representativeEntry).thenReturn(this)
whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
whenever(this.sbn).thenReturn(mockSbn)
- whenever(this.promotedNotificationContentModel).thenReturn(promotedContent)
+ whenever(this.promotedNotificationContentModels).thenReturn(promotedContent)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index ee698ae20adb..41120a16e4ea 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -33,8 +33,10 @@ import com.android.systemui.statusbar.notification.data.repository.notifications
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
import com.android.systemui.statusbar.notification.promoted.domain.interactor.aodPromotedNotificationInteractor
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Base
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.byIsAmbient
import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
import com.android.systemui.statusbar.notification.shared.byIsPromoted
@@ -354,6 +356,6 @@ private val testIcons =
private fun promotedContent(
key: String,
style: PromotedNotificationContentModel.Style,
-): PromotedNotificationContentModel {
- return PromotedNotificationContentModel.Builder(key).apply { this.style = style }.build()
+): PromotedNotificationContentModels {
+ return PromotedNotificationContentBuilder(key).applyToShared { this.style = style }.build()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index 0ac944a43de6..cc016b9768b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -33,18 +33,21 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_WAS_AUTOMATICALLY_PROMOTED
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.android.systemui.util.time.systemClock
import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertNotNull
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import org.junit.Test
@@ -112,12 +115,43 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setContentText(TEST_CONTENT_TEXT)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.subText).isEqualTo(TEST_SUB_TEXT)
- assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
+
+ content.publicVersion.apply {
+ assertThat(subText).isNull()
+ assertThat(title).isNull()
+ assertThat(text).isNull()
+ }
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+ fun extractsContent_commonFields_noRedaction() {
+ val entry = createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
+
+ val content = requireContent(entry, redactionType = REDACTION_TYPE_NONE)
+
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
+
+ content.publicVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
}
@Test
@@ -125,9 +159,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_wasPromotedAutomatically_false() {
val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content!!.wasPromotedAutomatically).isFalse()
+ assertThat(content.wasPromotedAutomatically).isFalse()
}
@Test
@@ -135,9 +169,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_wasPromotedAutomatically_true() {
val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, true) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content!!.wasPromotedAutomatically).isTrue()
+ assertThat(content.wasPromotedAutomatically).isTrue()
}
@Test
@@ -146,10 +180,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.text).isNull()
+ assertThat(content.text).isNull()
}
@Test
@@ -161,10 +194,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
+ assertThat(content.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
}
@Test
@@ -176,10 +208,9 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_noShortCriticalTextSet_textIsNull() {
val entry = createEntry { setShortCriticalText(null) }
- val content = extractContent(entry)
+ val content = requireContent(entry).privateVersion
- assertThat(content).isNotNull()
- assertThat(content?.shortCriticalText).isNull()
+ assertThat(content.shortCriticalText).isNull()
}
@Test
@@ -379,17 +410,14 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setWhen(providedCurrentTime)
}
- val content = extractContent(entry)
-
- assertThat(content).isNotNull()
+ val content = requireContent(entry).privateVersion
when (expected) {
- ExpectedTime.Null -> assertThat(content?.time).isNull()
+ ExpectedTime.Null -> assertThat(content.time).isNull()
ExpectedTime.Time -> {
- val actual = content?.time as? When.Time
- assertThat(actual).isNotNull()
- assertThat(actual?.currentTimeMillis).isEqualTo(expectedCurrentTime)
+ val actual = assertNotNull(content.time as? When.Time)
+ assertThat(actual.currentTimeMillis).isEqualTo(expectedCurrentTime)
}
ExpectedTime.CountDown,
@@ -398,23 +426,24 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
expectedCurrentTime + systemClock.elapsedRealtime() -
systemClock.currentTimeMillis()
- val actual = content?.time as? When.Chronometer
- assertThat(actual).isNotNull()
- assertThat(actual?.elapsedRealtimeMillis).isEqualTo(expectedElapsedRealtime)
- assertThat(actual?.isCountDown).isEqualTo(expected == ExpectedTime.CountDown)
+ val actual = assertNotNull(content.time as? When.Chronometer)
+ assertThat(actual.elapsedRealtimeMillis).isEqualTo(expectedElapsedRealtime)
+ assertThat(actual.isCountDown).isEqualTo(expected == ExpectedTime.CountDown)
}
}
}
+ // TODO: Add tests for the style of the publicVersion once we implement that
+
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun extractContent_fromBaseStyle() {
val entry = createEntry { setStyle(null) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Base)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
}
@Test
@@ -422,10 +451,10 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
fun extractContent_fromBigPictureStyle() {
val entry = createEntry { setStyle(BigPictureStyle()) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigPicture)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigPicture)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
}
@Test
@@ -442,12 +471,15 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -464,12 +496,15 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -486,12 +521,15 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.BigText)
- assertThat(content?.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content?.text).isEqualTo(TEST_CONTENT_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_CONTENT_TEXT)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -506,11 +544,14 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
)
val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Call)
- assertThat(content?.title).isEqualTo(TEST_PERSON_NAME)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Call)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_PERSON_NAME)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
}
@Test
@@ -524,13 +565,17 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Progress)
- assertThat(content?.newProgress).isNotNull()
- assertThat(content?.newProgress?.progress).isEqualTo(75)
- assertThat(content?.newProgress?.progressMax).isEqualTo(100)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Progress)
+ val newProgress = assertNotNull(content.privateVersion.newProgress)
+ assertThat(newProgress.progress).isEqualTo(75)
+ assertThat(newProgress.progressMax).isEqualTo(100)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ assertThat(content.publicVersion.newProgress).isNull()
}
@Test
@@ -540,10 +585,11 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
- assertThat(content).isNotNull()
- assertThat(content?.style).isEqualTo(Style.Ineligible)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Ineligible)
+
+ assertThat(content.publicVersion.style).isEqualTo(Style.Ineligible)
}
@Test
@@ -553,18 +599,13 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ false)
}
- val content = extractContent(entry)
-
- assertThat(content).isNotNull()
+ val content = requireContent(entry)
- val oldProgress = content?.oldProgress
- assertThat(oldProgress).isNotNull()
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- assertThat(content).isNotNull()
- assertThat(content?.oldProgress).isNotNull()
- assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
- assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(content?.oldProgress?.isIndeterminate).isFalse()
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isFalse()
}
@Test
@@ -574,18 +615,25 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ true)
}
- val content = extractContent(entry)
+ val content = requireContent(entry)
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- assertThat(content).isNotNull()
- assertThat(content?.oldProgress).isNotNull()
- assertThat(content?.oldProgress?.progress).isEqualTo(TEST_PROGRESS)
- assertThat(content?.oldProgress?.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(content?.oldProgress?.isIndeterminate).isTrue()
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isTrue()
}
- private fun extractContent(entry: NotificationEntry): PromotedNotificationContentModel? {
+ private fun requireContent(
+ entry: NotificationEntry,
+ redactionType: Int = REDACTION_TYPE_PUBLIC,
+ ): PromotedNotificationContentModels = assertNotNull(extractContent(entry, redactionType))
+
+ private fun extractContent(
+ entry: NotificationEntry,
+ redactionType: Int = REDACTION_TYPE_PUBLIC,
+ ): PromotedNotificationContentModels? {
val recoveredBuilder = Notification.Builder(context, entry.sbn.notification)
- return underTest.extractContent(entry, recoveredBuilder, imageModelProvider)
+ return underTest.extractContent(entry, recoveredBuilder, redactionType, imageModelProvider)
}
private fun createEntry(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
index 873ab5bc9c2c..42c3f6603ad8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractorTest.kt
@@ -44,7 +44,7 @@ import com.android.systemui.statusbar.notification.data.repository.activeNotific
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
import com.android.systemui.testKosmos
@@ -562,14 +562,14 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -608,14 +608,14 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
activeNotificationModel(
key = "notif1",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif1").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif1").build(),
)
)
activeNotificationListRepository.addNotif(
activeNotificationModel(
key = "notif2",
statusBarChipIcon = createStatusBarIconViewOrNull(),
- promotedContent = PromotedNotificationContentModel.Builder("notif2").build(),
+ promotedContent = PromotedNotificationContentBuilder("notif2").build(),
)
)
@@ -643,8 +643,7 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
collectLastValue(underTest.aodPromotedNotification)
// THEN the ron is first because the call has no content
- assertThat(topPromotedNotificationContent?.identity?.key)
- .isEqualTo("0|test_pkg|0|ron|0")
+ assertThat(topPromotedNotificationContent?.key).isEqualTo("0|test_pkg|0|ron|0")
}
@Test
@@ -663,8 +662,7 @@ class PromotedNotificationsInteractorTest : SysuiTestCase() {
collectLastValue(underTest.aodPromotedNotification)
// THEN the call is the top notification
- assertThat(topPromotedNotificationContent?.identity?.key)
- .isEqualTo("0|test_pkg|0|call|0")
+ assertThat(topPromotedNotificationContent?.key).isEqualTo("0|test_pkg|0|call|0")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 99f2596dbf1d..19b1046f1931 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -67,11 +67,11 @@ import com.android.systemui.media.controls.util.MediaFeatureFlag;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips;
import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
-import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -389,8 +389,8 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Test
@DisableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_notWhenBothFlagsDisabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
@@ -401,43 +401,43 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
- public void testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled()
+ public void testExtractsPromotedContent_whePromotedNotificationUiFlagEnabled()
throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
- final PromotedNotificationContentModel content =
- new PromotedNotificationContentModel.Builder("key").build();
+ final PromotedNotificationContentModels content =
+ new PromotedNotificationContentBuilder("key").build();
mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModel());
+ assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
@@ -448,7 +448,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertNull(mRow.getEntry().getPromotedNotificationContentModel());
+ assertNull(mRow.getEntry().getPromotedNotificationContentModels());
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index 95366568a37a..5ad4a4fab056 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -103,46 +103,6 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
row.resetMenu();
}
-
- @Test
- public void testNoAppOpsInSlowSwipe() {
- when(mRow.getShowSnooze()).thenReturn(false);
- Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
-
- NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // noti blocking
- assertEquals(1, container.getChildCount());
- }
-
- @Test
- public void testNoSnoozeInSlowSwipe() {
- when(mRow.getShowSnooze()).thenReturn(false);
- Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
-
- NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // just for noti blocking
- assertEquals(1, container.getChildCount());
- }
-
- @Test
- public void testSnoozeInSlowSwipe() {
- when(mRow.getShowSnooze()).thenReturn(true);
- Settings.Global.putInt(mContext.getContentResolver(), SHOW_NEW_NOTIF_DISMISS, 0);
-
- NotificationMenuRow row = new NotificationMenuRow(mContext, mPeopleNotificationIdentifier);
- row.createMenu(mRow);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // one for snooze and one for noti blocking
- assertEquals(2, container.getChildCount());
- }
-
@Test
public void testSlowSwipe_newDismiss() {
when(mRow.getShowSnooze()).thenReturn(true);
@@ -237,6 +197,7 @@ public class NotificationMenuRowTest extends LeakCheckedTest {
new NotificationMenuRow(mContext, mPeopleNotificationIdentifier));
doReturn(30f).when(row).getSnapBackThreshold();
doReturn(50f).when(row).getDismissThreshold();
+ doReturn(70).when(row).getSpaceForMenu();
when(row.isMenuOnLeft()).thenReturn(true);
when(row.getTranslation()).thenReturn(40f);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index 063a04ab9f37..dcba3e447dda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -44,7 +44,7 @@ import com.android.systemui.statusbar.notification.ConversationNotificationProce
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.FakePromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
@@ -456,7 +456,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@Test
@DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_notWhenBothFlagsDisabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
@@ -468,38 +468,38 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
@EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
@DisableFlags(PromotedNotificationUi.FLAG_NAME)
fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenBothFlagsEnabled() {
- val content = PromotedNotificationContentModel.Builder("key").build()
+ val content = PromotedNotificationContentBuilder("key").build()
promotedNotificationContentExtractor.resetForEntry(row.entry, content)
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModel)
+ Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
}
@Test
@@ -510,7 +510,7 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertNull(row.entry.promotedNotificationContentModel)
+ Assert.assertNull(row.entry.promotedNotificationContentModels)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 1ea41de63e64..716353945be2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -186,7 +186,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
.thenReturn(false);
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
@@ -198,7 +199,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
.thenReturn(false);
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, false /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
assertThat(mBiometricUnlockController.getBiometricType())
@@ -248,7 +250,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(eq(false));
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_UNLOCK_COLLAPSING);
@@ -327,7 +330,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_SHOW_BOUNCER);
}
@@ -359,7 +363,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
BiometricSourceType.FACE, true /* isStrongBiometric */);
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
assertThat(mBiometricUnlockController.getMode())
.isEqualTo(BiometricUnlockController.MODE_NONE);
}
@@ -438,17 +443,20 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
// WHEN udfps fails once - then don't show the bouncer yet
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
// WHEN udfps fails the second time - then don't show the bouncer yet
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
// WHEN udpfs fails the third time
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
// THEN show the bouncer
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true,
+ "BiometricUnlockController#MODE_SHOW_BOUNCER");
}
@Test
@@ -460,14 +468,16 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
- verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager, never()).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
// WHEN lockout is received
mBiometricUnlockController.onBiometricError(FingerprintManager.FINGERPRINT_ERROR_LOCKOUT,
"Lockout", BiometricSourceType.FINGERPRINT);
// THEN show bouncer
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(true,
+ "BiometricUnlockController#MODE_SHOW_BOUNCER");
}
@Test
@@ -544,7 +554,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
BiometricSourceType.FINGERPRINT, true /* isStrongBiometric */);
// THEN shows primary bouncer
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
}
@Test
@@ -554,7 +565,8 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
BiometricSourceType.FACE, false /* isStrongBiometric */);
// THEN shows primary bouncer
- verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean());
+ verify(mStatusBarKeyguardViewManager).showPrimaryBouncer(anyBoolean(),
+ eq("BiometricUnlockController#MODE_SHOW_BOUNCER"));
}
private void givenFingerprintModeUnlockCollapsing() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
index 1cc291199531..d9e256228428 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallbackTest.java
@@ -124,7 +124,8 @@ public class StatusBarRemoteInputCallbackTest extends SysuiTestCase {
mRemoteInputCallback.onLockedRemoteInput(
mock(ExpandableNotificationRow.class), mock(View.class));
- verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ verify(mStatusBarKeyguardViewManager).showBouncer(true,
+ "StatusBarRemoteInputCallback#onLockedRemoteInput");
}
@Test
@DisableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
index c58b4bc9953c..18074d53e87b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt
@@ -40,7 +40,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -170,7 +170,7 @@ class OngoingCallControllerTest : SysuiTestCase() {
@Test
fun interactorHasOngoingCallNotif_repoHasPromotedContent() =
testScope.runTest {
- val promotedContent = PromotedNotificationContentModel.Builder("ongoingNotif").build()
+ val promotedContent = PromotedNotificationContentBuilder("ongoingNotif").build()
setNotifOnRepo(
activeNotificationModel(
key = "ongoingNotif",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
index 84f1d5cd4895..c071327ae398 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractorTest.kt
@@ -29,7 +29,7 @@ import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.gesture.swipeStatusBarAwayGestureHandler
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentBuilder
import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallTestHelper.addOngoingCallState
@@ -75,7 +75,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
val startTimeMs = 1000L
val testIconView: StatusBarIconView = mock()
val testIntent: PendingIntent = mock()
- val testPromotedContent = PromotedNotificationContentModel.Builder(key).build()
+ val testPromotedContent = PromotedNotificationContentBuilder(key).build()
addOngoingCallState(
key = key,
startTimeMs = startTimeMs,
@@ -106,7 +106,7 @@ class OngoingCallInteractorTest : SysuiTestCase() {
val startTimeMs = 1000L
val testIconView: StatusBarIconView = mock()
val testIntent: PendingIntent = mock()
- val testPromotedContent = PromotedNotificationContentModel.Builder(key).build()
+ val testPromotedContent = PromotedNotificationContentBuilder(key).build()
addOngoingCallState(
key = key,
startTimeMs = startTimeMs,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt
new file mode 100644
index 000000000000..3cf787d50d2f
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryKairosAdapterTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CarrierMergedConnectionRepositoryKairosAdapterTest :
+ CarrierMergedConnectionRepositoryTestBase() {
+
+ var job: Job? = null
+ val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+ override fun recreateRepo(): MobileConnectionRepositoryKairosAdapter {
+ lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+ job?.cancel()
+ Mockito.clearInvocations(telephonyManager)
+ job =
+ testScope.backgroundScope.launch {
+ kairosNetwork.activateSpec {
+ val repo = activated {
+ CarrierMergedConnectionRepositoryKairos(
+ SUB_ID,
+ logger,
+ telephonyManager,
+ wifiRepository,
+ isInEcmMode = stateOf(false),
+ )
+ }
+ adapter =
+ MobileConnectionRepositoryKairosAdapter(
+ repo,
+ SystemUiCarrierConfig(SUB_ID, testCarrierConfig()),
+ )
+ Unit
+ }
+ }
+ testScope.runCurrent() // ensure the lateinit is set
+ return adapter
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index 8e55f2e9a31a..8a6829cf5d21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -43,16 +44,30 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
-class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
+class CarrierMergedConnectionRepositoryTest : CarrierMergedConnectionRepositoryTestBase() {
+ override fun recreateRepo() =
+ CarrierMergedConnectionRepository(
+ SUB_ID,
+ logger,
+ telephonyManager,
+ testScope.backgroundScope.coroutineContext,
+ testScope.backgroundScope,
+ wifiRepository,
+ )
+}
+
+abstract class CarrierMergedConnectionRepositoryTestBase : SysuiTestCase() {
- private lateinit var underTest: CarrierMergedConnectionRepository
+ protected lateinit var underTest: MobileConnectionRepository
- private lateinit var wifiRepository: FakeWifiRepository
- @Mock private lateinit var logger: TableLogBuffer
- @Mock private lateinit var telephonyManager: TelephonyManager
+ protected lateinit var wifiRepository: FakeWifiRepository
+ @Mock protected lateinit var logger: TableLogBuffer
+ @Mock protected lateinit var telephonyManager: TelephonyManager
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ protected val testDispatcher = UnconfinedTestDispatcher()
+ protected val testScope = TestScope(testDispatcher)
+
+ abstract fun recreateRepo(): MobileConnectionRepository
@Before
fun setUp() {
@@ -62,15 +77,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
wifiRepository = FakeWifiRepository()
- underTest =
- CarrierMergedConnectionRepository(
- SUB_ID,
- logger,
- telephonyManager,
- testScope.backgroundScope.coroutineContext,
- testScope.backgroundScope,
- wifiRepository,
- )
+ underTest = recreateRepo()
}
@Test
@@ -121,10 +128,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
)
assertThat(latest).isEqualTo(3)
@@ -141,26 +145,17 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
wifiRepository.setIsWifiEnabled(true)
wifiRepository.setIsWifiDefault(true)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
)
wifiRepository.setWifiActivity(
- DataActivityModel(
- hasActivityIn = true,
- hasActivityOut = false,
- )
+ DataActivityModel(hasActivityIn = true, hasActivityOut = false)
)
assertThat(latest!!.hasActivityIn).isTrue()
assertThat(latest!!.hasActivityOut).isFalse()
wifiRepository.setWifiActivity(
- DataActivityModel(
- hasActivityIn = false,
- hasActivityOut = true,
- )
+ DataActivityModel(hasActivityIn = false, hasActivityOut = true)
)
assertThat(latest!!.hasActivityIn).isFalse()
@@ -178,10 +173,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID + 10,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID + 10, level = 3)
)
assertThat(latestLevel).isNotEqualTo(3)
@@ -199,10 +191,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
)
wifiRepository.setIsWifiEnabled(false)
@@ -219,10 +208,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
)
wifiRepository.setIsWifiDefault(false)
@@ -280,6 +266,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
fun networkName_usesSimOperatorNameAsInitial() =
testScope.runTest {
whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+ underTest = recreateRepo()
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -293,6 +280,10 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
fun networkName_updatesOnNetworkUpdate() =
testScope.runTest {
whenever(telephonyManager.simOperatorName).thenReturn("Test SIM name")
+ underTest = recreateRepo()
+
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setIsWifiDefault(true)
var latest: NetworkNameModel? = null
val job = underTest.networkName.onEach { latest = it }.launchIn(this)
@@ -301,10 +292,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
whenever(telephonyManager.simOperatorName).thenReturn("New SIM name")
wifiRepository.setWifiNetwork(
- WifiNetworkModel.CarrierMerged.of(
- subscriptionId = SUB_ID,
- level = 3,
- )
+ WifiNetworkModel.CarrierMerged.of(subscriptionId = SUB_ID, level = 3)
)
assertThat(latest).isEqualTo(NetworkNameModel.SimDerived("New SIM name"))
@@ -320,7 +308,7 @@ class CarrierMergedConnectionRepositoryTest : SysuiTestCase() {
assertThat(latest).isTrue()
}
- private companion object {
+ companion object {
const val SUB_ID = 123
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt
new file mode 100644
index 000000000000..e72d0c27e632
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorKairosAdapterTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
+import com.android.systemui.activated
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.combine
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorKairosAdapterTest.Companion.wrapRepo
+import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconInteractorKairosAdapterTest : MobileIconInteractorTestBase() {
+
+ var job: Job? = null
+ val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+ override fun createInteractor(overrides: MobileIconCarrierIdOverrides): MobileIconInteractor {
+ lateinit var result: MobileIconInteractor
+ job?.cancel()
+ job =
+ testScope.backgroundScope.launch {
+ kairosNetwork.activateSpec {
+ val wrapped = wrap(mobileIconsInteractor)
+ result =
+ MobileIconInteractorKairosAdapter(
+ kairosImpl =
+ activated {
+ MobileIconInteractorKairosImpl(
+ defaultSubscriptionHasDataEnabled =
+ wrapped.activeDataConnectionHasDataEnabled,
+ alwaysShowDataRatIcon = wrapped.alwaysShowDataRatIcon,
+ alwaysUseCdmaLevel = wrapped.alwaysUseCdmaLevel,
+ isSingleCarrier = wrapped.isSingleCarrier,
+ mobileIsDefault = wrapped.mobileIsDefault,
+ defaultMobileIconMapping = wrapped.defaultMobileIconMapping,
+ defaultMobileIconGroup = wrapped.defaultMobileIconGroup,
+ isDefaultConnectionFailed =
+ wrapped.isDefaultConnectionFailed,
+ isForceHidden = wrapped.isForceHidden,
+ connectionRepository = wrapRepo(connectionRepository),
+ context = context,
+ carrierIdOverrides = overrides,
+ )
+ }
+ )
+ Unit
+ }
+ }
+ testScope.runCurrent() // ensure the lateinit is set
+ return result
+ }
+
+ /** Allows us to wrap a (likely fake) MobileIconsInteractor into a Kairos version. */
+ private fun BuildScope.wrap(interactor: MobileIconsInteractor): MobileIconsInteractorKairos {
+ val filteredSubscriptions = interactor.filteredSubscriptions.toState(emptyList())
+ val icons = interactor.icons.toState()
+ return InteractorWrapper(
+ mobileIsDefault = interactor.mobileIsDefault.toState(),
+ filteredSubscriptions = filteredSubscriptions,
+ icons =
+ combine(filteredSubscriptions, icons) { subs, icons ->
+ subs.zip(icons).associate { (subModel, icon) ->
+ subModel.subscriptionId to buildSpec { wrap(icon) }
+ }
+ }
+ .asIncremental()
+ .applyLatestSpecForKey(),
+ isStackable = interactor.isStackable.toState(),
+ activeDataConnectionHasDataEnabled =
+ interactor.activeDataConnectionHasDataEnabled.toState(),
+ activeDataIconInteractor =
+ interactor.activeDataIconInteractor.toState().mapLatestBuild {
+ it?.let { wrap(it) }
+ },
+ alwaysShowDataRatIcon = interactor.alwaysShowDataRatIcon.toState(),
+ alwaysUseCdmaLevel = interactor.alwaysUseCdmaLevel.toState(),
+ isSingleCarrier = interactor.isSingleCarrier.toState(),
+ defaultMobileIconMapping = interactor.defaultMobileIconMapping.toState(),
+ defaultMobileIconGroup = interactor.defaultMobileIconGroup.toState(),
+ isDefaultConnectionFailed = interactor.isDefaultConnectionFailed.toState(),
+ isUserSetUp = interactor.isUserSetUp.toState(),
+ isForceHidden = interactor.isForceHidden.toState(false),
+ isDeviceInEmergencyCallsOnlyMode =
+ interactor.isDeviceInEmergencyCallsOnlyMode.toState(false),
+ )
+ }
+
+ private fun BuildScope.wrap(interactor: MobileIconInteractor): MobileIconInteractorKairos =
+ // unused in tests
+ mock()
+
+ private class InteractorWrapper(
+ override val mobileIsDefault: State<Boolean>,
+ override val filteredSubscriptions: State<List<SubscriptionModel>>,
+ override val icons: Incremental<Int, MobileIconInteractorKairos>,
+ override val isStackable: State<Boolean>,
+ override val activeDataConnectionHasDataEnabled: State<Boolean>,
+ override val activeDataIconInteractor: State<MobileIconInteractorKairos?>,
+ override val alwaysShowDataRatIcon: State<Boolean>,
+ override val alwaysUseCdmaLevel: State<Boolean>,
+ override val isSingleCarrier: State<Boolean>,
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>>,
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup>,
+ override val isDefaultConnectionFailed: State<Boolean>,
+ override val isUserSetUp: State<Boolean>,
+ override val isForceHidden: State<Boolean>,
+ override val isDeviceInEmergencyCallsOnlyMode: State<Boolean>,
+ ) : MobileIconsInteractorKairos
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 8c70da718c08..974a47587c77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -22,6 +22,7 @@ import android.telephony.CellSignalStrength
import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.internal.telephony.flags.Flags
import com.android.settingslib.mobile.MobileIconCarrierIdOverrides
import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
import com.android.settingslib.mobile.TelephonyIcons
@@ -58,21 +59,40 @@ import org.mockito.ArgumentMatchers.anyString
@SmallTest
@RunWith(AndroidJUnit4::class)
-class MobileIconInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
+class MobileIconInteractorTest : MobileIconInteractorTestBase() {
+ override fun createInteractor(overrides: MobileIconCarrierIdOverrides) =
+ MobileIconInteractorImpl(
+ testScope.backgroundScope,
+ mobileIconsInteractor.activeDataConnectionHasDataEnabled,
+ mobileIconsInteractor.alwaysShowDataRatIcon,
+ mobileIconsInteractor.alwaysUseCdmaLevel,
+ mobileIconsInteractor.isSingleCarrier,
+ mobileIconsInteractor.mobileIsDefault,
+ mobileIconsInteractor.defaultMobileIconMapping,
+ mobileIconsInteractor.defaultMobileIconGroup,
+ mobileIconsInteractor.isDefaultConnectionFailed,
+ mobileIconsInteractor.isForceHidden,
+ connectionRepository,
+ context,
+ overrides,
+ )
+}
- private lateinit var underTest: MobileIconInteractor
- private val mobileMappingsProxy = FakeMobileMappingsProxy()
- private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+abstract class MobileIconInteractorTestBase : SysuiTestCase() {
+ protected val kosmos = testKosmos()
- private val connectionRepository =
+ protected lateinit var underTest: MobileIconInteractor
+ protected val mobileMappingsProxy = FakeMobileMappingsProxy()
+ protected val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+
+ protected val connectionRepository =
FakeMobileConnectionRepository(
SUB_1_ID,
logcatTableLogBuffer(kosmos, "MobileIconInteractorTest"),
)
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ protected val testDispatcher = UnconfinedTestDispatcher()
+ protected val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
@@ -835,24 +855,9 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(0)
}
- private fun createInteractor(
+ abstract fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
- ) =
- MobileIconInteractorImpl(
- testScope.backgroundScope,
- mobileIconsInteractor.activeDataConnectionHasDataEnabled,
- mobileIconsInteractor.alwaysShowDataRatIcon,
- mobileIconsInteractor.alwaysUseCdmaLevel,
- mobileIconsInteractor.isSingleCarrier,
- mobileIconsInteractor.mobileIsDefault,
- mobileIconsInteractor.defaultMobileIconMapping,
- mobileIconsInteractor.defaultMobileIconGroup,
- mobileIconsInteractor.isDefaultConnectionFailed,
- mobileIconsInteractor.isForceHidden,
- connectionRepository,
- context,
- overrides,
- )
+ ): MobileIconInteractor
companion object {
private const val GSM_LEVEL = 1
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt
new file mode 100644
index 000000000000..787731e14cd1
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorKairosAdapterTest.kt
@@ -0,0 +1,223 @@
+/*
+ * 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.statusbar.pipeline.mobile.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.KairosBuilder
+import com.android.systemui.flags.featureFlagsClassic
+import com.android.systemui.kairos.BuildScope
+import com.android.systemui.kairos.Events
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.Incremental
+import com.android.systemui.kairos.State
+import com.android.systemui.kairos.activateKairosActivatable
+import com.android.systemui.kairos.asIncremental
+import com.android.systemui.kairos.buildSpec
+import com.android.systemui.kairos.kairos
+import com.android.systemui.kairos.map
+import com.android.systemui.kairos.mapValues
+import com.android.systemui.kairos.stateOf
+import com.android.systemui.kairosBuilder
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.tableLogBufferFactory
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairos
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.repository.connectivityRepository
+import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalKairosApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileIconsInteractorKairosAdapterTest : MobileIconsInteractorTestBase() {
+ override fun Kosmos.createInteractor(): MobileIconsInteractor {
+ val userSetupRepo = FakeUserSetupRepository()
+ val repoK =
+ MobileConnectionsRepoWrapper(connectionsRepository).also {
+ activateKairosActivatable(it)
+ }
+ val kairosInteractor =
+ MobileIconsInteractorKairosImpl(
+ mobileConnectionsRepo = repoK,
+ carrierConfigTracker = carrierConfigTracker,
+ tableLogger = mock(),
+ connectivityRepository = connectivityRepository,
+ userSetupRepo = userSetupRepo,
+ context = context,
+ featureFlagsClassic = featureFlagsClassic,
+ )
+ .also { activateKairosActivatable(it) }
+ return MobileIconsInteractorKairosAdapter(
+ kairosInteractor = kairosInteractor,
+ repo = connectionsRepository,
+ repoK = repoK,
+ kairosNetwork = kairos,
+ scope = applicationCoroutineScope,
+ context = context,
+ mobileMappingsProxy = mobileMappingsProxy,
+ userSetupRepo = userSetupRepo,
+ logFactory = tableLogBufferFactory,
+ )
+ .also {
+ activateKairosActivatable(it)
+ runCurrent()
+ }
+ }
+
+ /** Allows us to wrap a (likely fake) MobileConnectionsRepository into a Kairos version. */
+ private class MobileConnectionsRepoWrapper(val unwrapped: MobileConnectionsRepository) :
+ MobileConnectionsRepositoryKairos, KairosBuilder by kairosBuilder() {
+
+ override val mobileConnectionsBySubId: Incremental<Int, MobileConnectionRepositoryKairos> =
+ buildIncremental {
+ unwrapped.subscriptions
+ .toState()
+ .map { it.associate { it.subscriptionId to Unit } }
+ .asIncremental()
+ .mapValues { (subId, _) ->
+ buildSpec { wrapRepo(unwrapped.getRepoForSubId(subId)) }
+ }
+ .applyLatestSpecForKey()
+ }
+ override val subscriptions: State<Collection<SubscriptionModel>> = buildState {
+ unwrapped.subscriptions.toState()
+ }
+ override val activeMobileDataSubscriptionId: State<Int?> = buildState {
+ unwrapped.activeMobileDataSubscriptionId.toState()
+ }
+ override val activeMobileDataRepository: State<MobileConnectionRepositoryKairos?> =
+ buildState {
+ unwrapped.activeMobileDataRepository.toState().mapLatestBuild {
+ it?.let { wrapRepo(it) }
+ }
+ }
+ override val activeSubChangedInGroupEvent: Events<Unit> = buildEvents {
+ unwrapped.activeSubChangedInGroupEvent.toEvents()
+ }
+ override val defaultDataSubId: State<Int?> = buildState {
+ unwrapped.defaultDataSubId.toState()
+ }
+ override val mobileIsDefault: State<Boolean> = buildState {
+ unwrapped.mobileIsDefault.toState()
+ }
+ override val hasCarrierMergedConnection: State<Boolean> = buildState {
+ unwrapped.hasCarrierMergedConnection.toState(false)
+ }
+ override val defaultConnectionIsValidated: State<Boolean> = buildState {
+ unwrapped.defaultConnectionIsValidated.toState()
+ }
+ override val defaultDataSubRatConfig: State<MobileMappings.Config> = buildState {
+ unwrapped.defaultDataSubRatConfig.toState()
+ }
+ override val defaultMobileIconMapping: State<Map<String, SignalIcon.MobileIconGroup>> =
+ buildState {
+ unwrapped.defaultMobileIconMapping.toState(emptyMap())
+ }
+ override val defaultMobileIconGroup: State<SignalIcon.MobileIconGroup> = buildState {
+ unwrapped.defaultMobileIconGroup.toState(TelephonyIcons.THREE_G)
+ }
+ override val isDeviceEmergencyCallCapable: State<Boolean> = buildState {
+ unwrapped.isDeviceEmergencyCallCapable.toState()
+ }
+ override val isAnySimSecure: State<Boolean> = buildState {
+ unwrapped.isDeviceEmergencyCallCapable.toState()
+ }
+ override val isInEcmMode: State<Boolean> = stateOf(false)
+ }
+
+ private class MobileConnectionRepoWrapper(
+ override val subId: Int,
+ override val carrierId: State<Int>,
+ override val inflateSignalStrength: State<Boolean>,
+ override val allowNetworkSliceIndicator: State<Boolean>,
+ override val tableLogBuffer: TableLogBuffer,
+ override val isEmergencyOnly: State<Boolean>,
+ override val isRoaming: State<Boolean>,
+ override val operatorAlphaShort: State<String?>,
+ override val isInService: State<Boolean>,
+ override val isNonTerrestrial: State<Boolean>,
+ override val isGsm: State<Boolean>,
+ override val cdmaLevel: State<Int>,
+ override val primaryLevel: State<Int>,
+ override val satelliteLevel: State<Int>,
+ override val dataConnectionState: State<DataConnectionState>,
+ override val dataActivityDirection: State<DataActivityModel>,
+ override val carrierNetworkChangeActive: State<Boolean>,
+ override val resolvedNetworkType: State<ResolvedNetworkType>,
+ override val numberOfLevels: State<Int>,
+ override val dataEnabled: State<Boolean>,
+ override val cdmaRoaming: State<Boolean>,
+ override val networkName: State<NetworkNameModel>,
+ override val carrierName: State<NetworkNameModel>,
+ override val isAllowedDuringAirplaneMode: State<Boolean>,
+ override val hasPrioritizedNetworkCapabilities: State<Boolean>,
+ override val isInEcmMode: State<Boolean>,
+ ) : MobileConnectionRepositoryKairos
+
+ companion object {
+ /** Allows us to wrap a (likely fake) MobileConnectionRepository into a Kairos version. */
+ fun BuildScope.wrapRepo(
+ conn: MobileConnectionRepository
+ ): MobileConnectionRepositoryKairos =
+ with(conn) {
+ MobileConnectionRepoWrapper(
+ subId = subId,
+ carrierId = carrierId.toState(),
+ inflateSignalStrength = inflateSignalStrength.toState(),
+ allowNetworkSliceIndicator = allowNetworkSliceIndicator.toState(),
+ tableLogBuffer = tableLogBuffer,
+ isEmergencyOnly = isEmergencyOnly.toState(),
+ isRoaming = isRoaming.toState(),
+ operatorAlphaShort = operatorAlphaShort.toState(),
+ isInService = isInService.toState(),
+ isNonTerrestrial = isNonTerrestrial.toState(),
+ isGsm = isGsm.toState(),
+ cdmaLevel = cdmaLevel.toState(),
+ primaryLevel = primaryLevel.toState(),
+ satelliteLevel = satelliteLevel.toState(),
+ dataConnectionState = dataConnectionState.toState(),
+ dataActivityDirection = dataActivityDirection.toState(),
+ carrierNetworkChangeActive = carrierNetworkChangeActive.toState(),
+ resolvedNetworkType = resolvedNetworkType.toState(),
+ numberOfLevels = numberOfLevels.toState(),
+ dataEnabled = dataEnabled.toState(),
+ cdmaRoaming = cdmaRoaming.toState(),
+ networkName = networkName.toState(),
+ carrierName = carrierName.toState(),
+ isAllowedDuringAirplaneMode = isAllowedDuringAirplaneMode.toState(),
+ hasPrioritizedNetworkCapabilities = hasPrioritizedNetworkCapabilities.toState(),
+ isInEcmMode = stateOf(false),
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 9e914ad0a660..356e5676954e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -58,8 +58,35 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
-class MobileIconsInteractorTest : SysuiTestCase() {
- private val kosmos by lazy {
+class MobileIconsInteractorTest : MobileIconsInteractorTestBase() {
+ override fun Kosmos.createInteractor() =
+ MobileIconsInteractorImpl(
+ mobileConnectionsRepository,
+ carrierConfigTracker,
+ tableLogger = mock(),
+ connectivityRepository,
+ FakeUserSetupRepository(),
+ testScope.backgroundScope,
+ context,
+ featureFlagsClassic,
+ )
+
+ @Test
+ fun iconInteractor_cachedPerSubId() =
+ kosmos.runTest {
+ connectionsRepository.setSubscriptions(listOf(SUB_1))
+ runCurrent()
+
+ val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+ val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
+
+ assertThat(interactor1).isNotNull()
+ assertThat(interactor1).isSameInstanceAs(interactor2)
+ }
+}
+
+abstract class MobileIconsInteractorTestBase : SysuiTestCase() {
+ protected val kosmos by lazy {
testKosmos().apply {
mobileConnectionsRepositoryLogbufferName = "MobileIconsInteractorTest"
mobileConnectionsRepository.fake.run {
@@ -78,22 +105,13 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
// shortcut rename
- private val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
+ protected val Kosmos.connectionsRepository by Fixture { mobileConnectionsRepository.fake }
- private val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
+ protected val Kosmos.carrierConfigTracker by Fixture { mock<CarrierConfigTracker>() }
- private val Kosmos.underTest by Fixture {
- MobileIconsInteractorImpl(
- mobileConnectionsRepository,
- carrierConfigTracker,
- tableLogger = mock(),
- connectivityRepository,
- FakeUserSetupRepository(),
- testScope.backgroundScope,
- context,
- featureFlagsClassic,
- )
- }
+ protected val Kosmos.underTest by Fixture { createInteractor() }
+
+ abstract fun Kosmos.createInteractor(): MobileIconsInteractor
@Test
fun filteredSubscriptions_default() =
@@ -744,12 +762,15 @@ class MobileIconsInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.mobileIsDefault)
connectionsRepository.mobileIsDefault.value = true
+ runCurrent()
assertThat(latest).isTrue()
connectionsRepository.mobileIsDefault.value = false
+ runCurrent()
assertThat(latest).isFalse()
connectionsRepository.hasCarrierMergedConnection.value = true
+ runCurrent()
assertThat(latest).isTrue()
}
@@ -874,16 +895,6 @@ class MobileIconsInteractorTest : SysuiTestCase() {
}
@Test
- fun iconInteractor_cachedPerSubId() =
- kosmos.runTest {
- val interactor1 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
- val interactor2 = underTest.getMobileConnectionInteractorForSubId(SUB_1_ID)
-
- assertThat(interactor1).isNotNull()
- assertThat(interactor1).isSameInstanceAs(interactor2)
- }
-
- @Test
fun deviceBasedEmergencyMode_emergencyCallsOnly_followsDeviceServiceStateFromRepo() =
kosmos.runTest {
val latest by collectLastValue(underTest.isDeviceInEmergencyCallsOnlyMode)
@@ -1007,8 +1018,8 @@ class MobileIconsInteractorTest : SysuiTestCase() {
companion object {
- private const val SUB_1_ID = 1
- private val SUB_1 =
+ const val SUB_1_ID = 1
+ val SUB_1 =
SubscriptionModel(
subscriptionId = SUB_1_ID,
carrierName = "Carrier $SUB_1_ID",
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
index 6d3813c90bfd..bcc6ea3d3b35 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/topwindoweffects/TopLevelWindowEffectsTest.kt
@@ -20,8 +20,6 @@ import android.view.View
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository
import com.android.systemui.keyevent.data.repository.keyEventRepository
@@ -58,20 +56,13 @@ class TopLevelWindowEffectsTest : SysuiTestCase() {
private lateinit var windowManager: WindowManager
@Mock
- private lateinit var viewCapture: Lazy<ViewCapture>
-
- @Mock
private lateinit var viewModelFactory: SqueezeEffectViewModel.Factory
private val Kosmos.underTest by Kosmos.Fixture {
TopLevelWindowEffects(
context = mContext,
applicationScope = testScope.backgroundScope,
- windowManager = ViewCaptureAwareWindowManager(
- windowManager = windowManager,
- lazyViewCapture = viewCapture,
- isViewCaptureEnabled = false
- ),
+ windowManager = windowManager,
keyEventInteractor = keyEventInteractor,
viewModelFactory = viewModelFactory,
squeezeEffectInteractor = SqueezeEffectInteractor(
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 18a124cf362e..033503f9ad8e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -52,6 +52,7 @@ import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABL
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.SCREEN_EVENT_TIMEOUT
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
+import com.android.systemui.unfold.data.repository.ScreenTimeoutPolicyRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.unfoldedDeviceState
@@ -97,6 +98,8 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
private val animationStatusRepository = kosmos.fakeAnimationStatusRepository
private val keyguardInteractor = mock<KeyguardInteractor>()
private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
+ private val screenTimeoutPolicyRepository = mock<ScreenTimeoutPolicyRepository>()
+ private val screenTimeoutActive = MutableStateFlow(true)
private val latencyTracker = mock<LatencyTracker>()
private val deviceStateManager = kosmos.deviceStateManager
@@ -136,6 +139,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
.thenReturn(nonEmptyClosedDeviceStatesArray)
whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable)
+ whenever(screenTimeoutPolicyRepository.screenTimeoutActive).thenReturn(screenTimeoutActive)
animationStatusRepository.onAnimationStatusChanged(true)
powerInteractor.setAwakeForTest()
powerInteractor.setScreenPowerState(SCREEN_ON)
@@ -144,6 +148,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
mockContext,
foldStateRepository,
powerInteractor,
+ screenTimeoutPolicyRepository,
unfoldTransitionInteractor,
animationStatusRepository,
keyguardInteractor,
@@ -196,6 +201,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
mockContext,
foldStateRepository,
powerInteractor,
+ screenTimeoutPolicyRepository,
unfoldTransitionInteractorWithEmptyProgressProvider,
animationStatusRepository,
keyguardInteractor,
@@ -625,6 +631,44 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
}
}
+ @Test
+ fun displaySwitch_screenTimeoutActive_logsNoScreenWakelocks() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+ screenTimeoutActive.value = true
+
+ startUnfolding()
+ advanceTimeBy(100.milliseconds)
+ finishUnfolding()
+
+ val event = capturedLogEvent()
+ assertThat(event.screenWakelockStatus)
+ .isEqualTo(
+ SysUiStatsLog
+ .DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_NO_WAKELOCKS
+ )
+ }
+ }
+
+ @Test
+ fun displaySwitch_screenTimeoutNotActive_logsHasScreenWakelocks() {
+ testScope.runTest {
+ startInFoldedState(displaySwitchLatencyTracker)
+ screenTimeoutActive.value = false
+
+ startUnfolding()
+ advanceTimeBy(100.milliseconds)
+ finishUnfolding()
+
+ val event = capturedLogEvent()
+ assertThat(event.screenWakelockStatus)
+ .isEqualTo(
+ SysUiStatsLog
+ .DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_HAS_SCREEN_WAKELOCKS
+ )
+ }
+ }
+
private fun capturedLogEvent(): DisplaySwitchLatencyEvent {
verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
return loggerArgumentCaptor.value
@@ -662,6 +706,9 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
fromFoldableDeviceState = fromFoldableDeviceState,
toFoldableDeviceState = toFoldableDeviceState,
toState = toState,
+ screenWakelockStatus =
+ SysUiStatsLog
+ .DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_NO_WAKELOCKS,
trackingResult = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__SUCCESS,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index 3eada258f616..07706414393b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -27,6 +27,8 @@ import android.graphics.drawable.Drawable
import android.os.Process
import android.os.UserHandle
import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -34,6 +36,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION
import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
import com.android.systemui.SysuiTestCase
@@ -68,6 +71,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertNotNull
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
@@ -101,10 +105,13 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var userLogoutInteractor: UserLogoutInteractor
private val kosmos = testKosmos()
+ private val logoutEnabledStateFlow = MutableStateFlow<Boolean>(false)
private val testScope = kosmos.testScope
private lateinit var spyContext: Context
+
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
private lateinit var keyguardRepository: FakeKeyguardRepository
@@ -118,6 +125,8 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
whenever(manager.getUserIcon(anyInt())).thenReturn(ICON)
whenever(manager.canAddMoreUsers(any())).thenReturn(true)
+ whenever(userLogoutInteractor.isLogoutEnabled).thenReturn(logoutEnabledStateFlow)
+
overrideResource(com.android.settingslib.R.drawable.ic_account_circle, GUEST_ICON)
overrideResource(R.dimen.max_avatar_size, 10)
overrideResource(
@@ -493,6 +502,42 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION)
+ fun actions_logoutEnabled_flagDisabled_signOutIsNotShown() {
+ createUserInteractor()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 1, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+ keyguardRepository.setKeyguardShowing(true)
+ logoutEnabledStateFlow.value = true
+
+ val value = collectLastValue(underTest.actions)
+
+ assertThat(value()).isEqualTo(emptyList<UserActionModel>())
+ }
+ }
+
+ @Test
+ @EnableFlags(FLAG_USER_SWITCHER_ADD_SIGN_OUT_OPTION)
+ fun actions_logoutEnabled_flagEnabled_signOutIsShown() {
+ createUserInteractor()
+ testScope.runTest {
+ val userInfos = createUserInfos(count = 1, includeGuest = false)
+ userRepository.setUserInfos(userInfos)
+ userRepository.setSelectedUserInfo(userInfos[0])
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = false))
+ keyguardRepository.setKeyguardShowing(true)
+ logoutEnabledStateFlow.value = true
+
+ val value = collectLastValue(underTest.actions)
+
+ assertThat(value()).isEqualTo(listOf(UserActionModel.SIGN_OUT))
+ }
+ }
+
+ @Test
fun executeAction_addUser_dismissesDialogAndStartsActivity() {
createUserInteractor()
testScope.runTest {
@@ -569,14 +614,23 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
verify(uiEventLogger, times(1))
.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
assertThat(dialogRequests)
- .contains(
- ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
- )
+ .contains(ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true))
verify(activityManager).switchUser(guestUserInfo.id)
}
}
@Test
+ fun executeAction_signOut() {
+ createUserInteractor()
+ testScope.runTest {
+ underTest.executeAction(UserActionModel.SIGN_OUT)
+ runCurrent()
+
+ verify(userLogoutInteractor).logOut()
+ }
+ }
+
+ @Test
fun selectUser_alreadySelectedGuestReSelected_exitGuestDialog() {
createUserInteractor()
testScope.runTest {
@@ -739,7 +793,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
spyContext,
- Intent(Intent.ACTION_LOCALE_CHANGED)
+ Intent(Intent.ACTION_LOCALE_CHANGED),
)
runCurrent()
@@ -972,7 +1026,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
50,
"Work Profile",
/* iconPath= */ "",
- /* flags= */ UserInfo.FLAG_MANAGED_PROFILE
+ /* flags= */ UserInfo.FLAG_MANAGED_PROFILE,
)
)
userRepository.setUserInfos(userInfos)
@@ -1010,7 +1064,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
userRepository.setSettings(
UserSwitcherSettingsModel(
isUserSwitcherEnabled = true,
- isAddUsersFromLockscreen = true
+ isAddUsersFromLockscreen = true,
)
)
@@ -1034,7 +1088,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
userRepository.setSettings(
UserSwitcherSettingsModel(
isUserSwitcherEnabled = true,
- isAddUsersFromLockscreen = true
+ isAddUsersFromLockscreen = true,
)
)
@@ -1068,7 +1122,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
whenever(
manager.hasUserRestrictionForUser(
UserManager.DISALLOW_ADD_USER,
- UserHandle.of(id)
+ UserHandle.of(id),
)
)
.thenReturn(true)
@@ -1170,7 +1224,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
whenever(
manager.hasUserRestrictionForUser(
UserManager.DISALLOW_ADD_USER,
- UserHandle.of(0)
+ UserHandle.of(0),
)
)
.thenReturn(true)
@@ -1195,7 +1249,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
model = model,
id = index,
isSelected = index == selectedIndex,
- isGuest = includeGuest && index == count - 1
+ isGuest = includeGuest && index == count - 1,
)
}
}
@@ -1263,14 +1317,12 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
assertThat(record.isSwitchToEnabled).isEqualTo(isSwitchToEnabled)
}
- private fun assertRecordForAction(
- record: UserRecord,
- type: UserActionModel,
- ) {
+ private fun assertRecordForAction(record: UserRecord, type: UserActionModel) {
assertThat(record.isGuest).isEqualTo(type == UserActionModel.ENTER_GUEST_MODE)
assertThat(record.isAddUser).isEqualTo(type == UserActionModel.ADD_USER)
assertThat(record.isAddSupervisedUser)
.isEqualTo(type == UserActionModel.ADD_SUPERVISED_USER)
+ assertThat(record.isSignOut).isEqualTo(type === UserActionModel.SIGN_OUT)
}
private fun createUserInteractor(startAsProcessUser: Boolean = true) {
@@ -1317,13 +1369,11 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
featureFlags = kosmos.fakeFeatureFlagsClassic,
userRestrictionChecker = mock(),
processWrapper = kosmos.processWrapper,
+ userLogoutInteractor = userLogoutInteractor,
)
}
- private fun createUserInfos(
- count: Int,
- includeGuest: Boolean,
- ): List<UserInfo> {
+ private fun createUserInfos(count: Int, includeGuest: Boolean): List<UserInfo> {
return (0 until count).map { index ->
val isGuest = includeGuest && index == count - 1
createUserInfo(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 5d51c6d16c5a..d51e66d6f3b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -44,9 +44,12 @@ import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.toList
@@ -78,13 +81,13 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var userLogoutInteractor: UserLogoutInteractor
private lateinit var underTest: StatusBarUserChipViewModel
private val userRepository = FakeUserRepository()
private lateinit var guestUserInteractor: GuestUserInteractor
private lateinit var refreshUsersScheduler: RefreshUsersScheduler
-
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -92,6 +95,9 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
+ val logoutEnabledStateFlow = MutableStateFlow<Boolean>(false)
+ whenever(userLogoutInteractor.isLogoutEnabled).thenReturn(logoutEnabledStateFlow)
+
doAnswer { invocation ->
val userId = invocation.arguments[0] as Int
when (userId) {
@@ -251,9 +257,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
- TelephonyInteractor(
- repository = FakeTelephonyRepository(),
- ),
+ TelephonyInteractor(repository = FakeTelephonyRepository()),
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
@@ -263,7 +267,8 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
userRestrictionChecker = mock(),
- processWrapper = ProcessWrapperFake(activityManager)
+ processWrapper = ProcessWrapperFake(activityManager),
+ userLogoutInteractor = userLogoutInteractor,
)
)
}
@@ -293,7 +298,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
USER_NAME_0.text!!,
/* iconPath */ "",
/* flags */ UserInfo.FLAG_FULL,
- /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM,
)
private val USER_1 =
@@ -302,7 +307,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
USER_NAME_1.text!!,
/* iconPath */ "",
/* flags */ UserInfo.FLAG_FULL,
- /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM,
)
private val USER_2 =
@@ -311,7 +316,7 @@ class StatusBarUserChipViewModelTest : SysuiTestCase() {
USER_NAME_2.text!!,
/* iconPath */ "",
/* flags */ UserInfo.FLAG_FULL,
- /* userType */ UserManager.USER_TYPE_FULL_SYSTEM
+ /* userType */ UserManager.USER_TYPE_FULL_SYSTEM,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 8ff088f5d29b..087ccb83afe5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -44,6 +44,7 @@ import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.domain.interactor.GuestUserInteractor
import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserLogoutInteractor
import com.android.systemui.user.domain.interactor.UserSwitcherInteractor
import com.android.systemui.user.legacyhelper.ui.LegacyUserUiHelper
import com.android.systemui.user.shared.model.UserActionModel
@@ -51,6 +52,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
@@ -79,6 +81,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
@Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+ @Mock private lateinit var userLogoutInteractor: UserLogoutInteractor
private lateinit var underTest: UserSwitcherViewModel
@@ -94,6 +97,10 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
whenever(manager.canAddMoreUsers(any())).thenReturn(true)
whenever(manager.getUserSwitchability(any()))
.thenReturn(UserManager.SWITCHABILITY_STATUS_OK)
+
+ val logoutEnabledStateFlow = MutableStateFlow<Boolean>(false)
+ whenever(userLogoutInteractor.isLogoutEnabled).thenReturn(logoutEnabledStateFlow)
+
overrideResource(
com.android.internal.R.string.config_supervisedUserCreationPackage,
SUPERVISED_USER_CREATION_PACKAGE,
@@ -113,15 +120,11 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
UserInfo.FLAG_ADMIN or
UserInfo.FLAG_FULL,
UserManager.USER_TYPE_FULL_SYSTEM,
- ),
+ )
)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
- userRepository.setSettings(
- UserSwitcherSettingsModel(
- isUserSwitcherEnabled = true,
- )
- )
+ userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
}
val refreshUsersScheduler =
@@ -163,9 +166,7 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
telephonyInteractor =
- TelephonyInteractor(
- repository = FakeTelephonyRepository(),
- ),
+ TelephonyInteractor(repository = FakeTelephonyRepository()),
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
backgroundDispatcher = testDispatcher,
@@ -175,7 +176,8 @@ class UserSwitcherViewModelTest : SysuiTestCase() {
guestUserInteractor = guestUserInteractor,
uiEventLogger = uiEventLogger,
userRestrictionChecker = mock(),
- processWrapper = ProcessWrapperFake(activityManager)
+ processWrapper = ProcessWrapperFake(activityManager),
+ userLogoutInteractor = userLogoutInteractor,
),
guestUserInteractor = guestUserInteractor,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
index a303da0dae1a..49c06bf77fb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogVisibilityInteractorTest.kt
@@ -143,6 +143,7 @@ class VolumeDialogVisibilityInteractorTest : SysuiTestCase() {
assertThat(visibilityModel)
.isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+ assertThat(fakeVolumeDialogController.hasUserActivity).isTrue()
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelTest.kt
new file mode 100644
index 000000000000..0847281d9b42
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelTest.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.volume.dialog.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.accessibilityRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.Events
+import com.android.systemui.volume.dialog.data.repository.volumeDialogVisibilityRepository
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
+import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class VolumeDialogPluginViewModelTest : SysuiTestCase() {
+
+ private val kosmos: Kosmos = testKosmos()
+
+ private val underTest: VolumeDialogPluginViewModel by lazy {
+ kosmos.volumeDialogPluginViewModel
+ }
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ volumeDialogVisibilityRepository.updateVisibility {
+ VolumeDialogVisibilityModel.Visible(Events.SHOW_REASON_VOLUME_CHANGED, false, 0)
+ }
+ }
+
+ @Test
+ fun safetyWarningAppears_timeoutReset() =
+ kosmos.runTest {
+ accessibilityRepository.setRecommendedTimeout(3.seconds)
+ val visibility by collectLastValue(volumeDialogVisibilityInteractor.dialogVisibility)
+ testScope.advanceTimeBy(2.seconds)
+ assertThat(visibility).isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+
+ underTest.onSafetyWarningDialogShown()
+ testScope.advanceTimeBy(2.seconds)
+ assertThat(visibility).isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+ }
+
+ @Test
+ fun csdWarningAppears_timeoutReset() =
+ kosmos.runTest {
+ accessibilityRepository.setRecommendedTimeout(3.seconds)
+ val visibility by collectLastValue(volumeDialogVisibilityInteractor.dialogVisibility)
+ testScope.advanceTimeBy(2.seconds)
+ assertThat(visibility).isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+
+ underTest.onCsdWarningDialogShown()
+ testScope.advanceTimeBy(2.seconds)
+ assertThat(visibility).isInstanceOf(VolumeDialogVisibilityModel.Visible::class.java)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 60a15915fb77..046b5d701ddf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -57,6 +57,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import org.junit.Before;
import org.junit.Test;
@@ -89,6 +90,8 @@ public class ImageWallpaperTest extends SysuiTestCase {
private Context mMockContext;
@Mock
private UserTracker mUserTracker;
+ @Mock
+ private WindowManagerProvider mWindowManagerProvider;
@Mock
private Bitmap mWallpaperBitmap;
@@ -105,7 +108,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
when(mWindowMetrics.getBounds()).thenReturn(
new Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
- when(mMockContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager);
+ when(mWindowManagerProvider.getWindowManager(any())).thenReturn(mWindowManager);
// set up display manager
doNothing().when(mDisplayManager).registerDisplayListener(any(), any());
@@ -182,7 +185,7 @@ public class ImageWallpaperTest extends SysuiTestCase {
}
private ImageWallpaper createImageWallpaper() {
- return new ImageWallpaper(mFakeExecutor, mUserTracker) {
+ return new ImageWallpaper(mFakeExecutor, mUserTracker, mWindowManagerProvider) {
@Override
public Engine onCreateEngine() {
return new CanvasEngine() {
diff --git a/packages/SystemUI/res/drawable/unpin_icon.xml b/packages/SystemUI/res/drawable/unpin_icon.xml
new file mode 100644
index 000000000000..4e2e15893884
--- /dev/null
+++ b/packages/SystemUI/res/drawable/unpin_icon.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M680,120L680,200L640,200L640,527L560,447L560,200L400,200L400,287L313,200L280,167L280,167L280,120L680,120ZM480,920L440,880L440,640L240,640L240,560L320,480L320,434L56,168L112,112L848,848L790,904L526,640L520,640L520,880L480,920ZM354,560L446,560L402,516L400,514L354,560ZM480,367L480,367L480,367L480,367ZM402,516L402,516L402,516L402,516Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
index aec204f45aa7..7f6dc49505bb 100644
--- a/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
+++ b/packages/SystemUI/res/drawable/vector_drawable_progress_indeterminate_horizontal_trimmed.xml
@@ -38,7 +38,7 @@
<path
android:name="rect"
android:pathData="M -144.0,-5.0 l 288.0,0 l 0,10.0 l -288.0,0 Z"
- android:fillColor="?androidprv:attr/colorAccentPrimaryVariant" />
+ android:fillColor="@androidprv:color/materialColorPrimary" />
</group>
</group>
</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/activity_rear_display_enabled.xml b/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
index f900626b4da6..6b633e03f1f2 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_enabled.xml
@@ -56,6 +56,7 @@
android:gravity="center_horizontal" />
<TextView
+ android:id="@+id/seekbar_instructions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rear_display_unfolded_front_screen_on_slide_to_cancel"
@@ -73,4 +74,13 @@
android:background="@null"
android:gravity="center_horizontal" />
+ <Button
+ android:id="@+id/cancel_button"
+ android:text="@string/cancel"
+ android:layout_width="@dimen/rear_display_animation_width_opened"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:visibility="gone"
+ style="@style/Widget.Dialog.Button.BorderButton"/>
+
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
index 43808f215a81..4002f7808637 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml
@@ -21,6 +21,9 @@
style="@style/AuthCredentialPanelStyle"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityLiveRegion="assertive"
+ android:importantForAccessibility="yes"
+ android:clickable="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/rightGuideline"
app:layout_constraintStart_toStartOf="@id/leftGuideline"
diff --git a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
index 51117a7845df..3c8cb6860a41 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml
@@ -22,6 +22,9 @@ android:layout_height="match_parent">
style="@style/AuthCredentialPanelStyle"
android:layout_width="0dp"
android:layout_height="0dp"
+ android:accessibilityLiveRegion="assertive"
+ android:importantForAccessibility="yes"
+ android:clickable="false"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp"
android:visibility="visible"
diff --git a/packages/SystemUI/res/layout/promoted_permission_guts.xml b/packages/SystemUI/res/layout/promoted_permission_guts.xml
new file mode 100644
index 000000000000..50e5ae3c05ed
--- /dev/null
+++ b/packages/SystemUI/res/layout/promoted_permission_guts.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2017, The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<com.android.systemui.statusbar.notification.row.PromotedPermissionGutsContent
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="2dp"
+ android:paddingBottom="2dp"
+ android:background="@androidprv:color/materialColorSurfaceContainerHigh"
+ android:theme="@style/Theme.SystemUI"
+ >
+
+ <RelativeLayout
+ android:id="@+id/promoted_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_2025_min_height">
+
+ <ImageView
+ android:id="@+id/unpin_icon"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/unpin_icon"
+ android:layout_alignParentTop="true"
+ android:layout_centerHorizontal="true"
+ android:padding="@dimen/notification_importance_button_padding"
+ />
+
+ <TextView
+ android:id="@+id/demote_explain"
+ android:layout_width="400dp"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_below="@id/unpin_icon"
+ android:layout_toLeftOf="@id/undo"
+ android:padding="@*android:dimen/notification_content_margin_end"
+ android:textColor="@androidprv:color/materialColorOnSurface"
+ android:minWidth="@dimen/min_clickable_item_size"
+ android:minHeight="@dimen/min_clickable_item_size"
+ style="@style/TextAppearance.NotificationInfo.Button" />
+
+ <TextView
+ android:id="@+id/undo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/unpin_icon"
+ android:layout_alignParentRight="true"
+ android:padding="@*android:dimen/notification_content_margin_end"
+ android:textColor="@androidprv:color/materialColorOnSurface"
+ android:minWidth="@dimen/min_clickable_item_size"
+ android:minHeight="@dimen/min_clickable_item_size"
+ android:text="@string/snooze_undo"
+ style="@style/TextAppearance.NotificationInfo.Button" />
+ </RelativeLayout>
+
+</com.android.systemui.statusbar.notification.row.PromotedPermissionGutsContent>
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index a1fa54cf592d..e094155f9041 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -16,17 +16,18 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/volume_dialog"
- android:layout_width="match_parent"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
android:alpha="0"
- android:clipChildren="false">
+ android:clipChildren="false"
+ android:minWidth="@dimen/volume_dialog_window_width">
<View
android:id="@+id/volume_dialog_background"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="0dp"
android:layout_marginTop="@dimen/volume_dialog_background_top_margin"
- android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
+ android:layout_marginBottom="@dimen/volume_dialog_background_margin_negative"
android:background="@drawable/volume_dialog_background"
app:layout_constraintBottom_toBottomOf="@id/volume_dialog_bottom_section_container"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml
index db800aa4a873..1dba96cafb70 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider_floating.xml
@@ -17,8 +17,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/volume_dialog_floating_slider_background"
- android:paddingHorizontal="@dimen/volume_dialog_floating_sliders_horizontal_padding"
- android:paddingVertical="@dimen/volume_dialog_floating_sliders_vertical_padding">
+ android:paddingHorizontal="@dimen/volume_dialog_floating_sliders_padding"
+ android:paddingVertical="@dimen/volume_dialog_floating_sliders_padding">
<include layout="@layout/volume_dialog_slider" />
</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog_top_section.xml b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
index b7455471d9a6..9d3598800bbf 100644
--- a/packages/SystemUI/res/layout/volume_dialog_top_section.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_top_section.xml
@@ -22,15 +22,15 @@
android:clipChildren="false"
android:clipToPadding="false"
android:gravity="center"
- android:paddingEnd="@dimen/volume_dialog_buttons_margin"
+ android:paddingEnd="@dimen/volume_dialog_background_margin"
app:layoutDescription="@xml/volume_dialog_ringer_drawer_motion_scene">
<View
android:id="@+id/ringer_buttons_background"
android:layout_width="@dimen/volume_dialog_width"
android:layout_height="0dp"
- android:layout_marginTop="@dimen/volume_dialog_background_vertical_margin"
- android:layout_marginBottom="@dimen/volume_dialog_background_vertical_margin"
+ android:layout_marginTop="@dimen/volume_dialog_background_margin_negative"
+ android:layout_marginBottom="@dimen/volume_dialog_background_margin_negative"
android:background="@drawable/volume_dialog_ringer_background"
android:visibility="gone" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index d0ae307b6919..55e94028b95e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -989,7 +989,7 @@
<dimen name="keyguard_security_container_padding_top">20dp</dimen>
- <dimen name="keyguard_translate_distance_on_swipe_up">-200dp</dimen>
+ <dimen name="keyguard_translate_distance_on_swipe_up">-180dp</dimen>
<dimen name="keyguard_indication_margin_bottom">32dp</dimen>
<dimen name="ambient_indication_margin_bottom">71dp</dimen>
@@ -2158,28 +2158,25 @@
<dimen name="contextual_edu_dialog_elevation">2dp</dimen>
<!-- Volume start -->
+ <dimen name="volume_dialog_window_width">176dp</dimen>
<dimen name="volume_dialog_width">60dp</dimen>
<dimen name="volume_dialog_background_corner_radius">30dp</dimen>
- <dimen name="volume_dialog_background_vertical_margin">
- @dimen/volume_dialog_buttons_margin_negative
- </dimen>
<!-- top margin covers half the ringer button + components spacing -->
<dimen name="volume_dialog_background_top_margin">-28dp</dimen>
+ <dimen name="volume_dialog_background_margin">10dp</dimen>
+ <dimen name="volume_dialog_background_margin_negative">-10dp</dimen>
- <dimen name="volume_dialog_window_margin">14dp</dimen>
+ <dimen name="volume_dialog_window_margin">12dp</dimen>
<dimen name="volume_dialog_components_spacing">10dp</dimen>
<dimen name="volume_dialog_floating_sliders_spacing">8dp</dimen>
- <dimen name="volume_dialog_floating_sliders_vertical_padding">10dp</dimen>
<dimen name="volume_dialog_floating_sliders_vertical_padding_negative">
- @dimen/volume_dialog_buttons_margin_negative
+ @dimen/volume_dialog_background_margin_negative
</dimen>
- <dimen name="volume_dialog_floating_sliders_horizontal_padding">4dp</dimen>
+ <dimen name="volume_dialog_floating_sliders_padding">4dp</dimen>
<dimen name="volume_dialog_button_size">40dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
- <dimen name="volume_dialog_buttons_margin">10dp</dimen>
- <dimen name="volume_dialog_buttons_margin_negative">-10dp</dimen>
<!--
A primary goal of this margin is to vertically constraint slider height in the landscape
orientation when the vertical space is limited
@@ -2190,8 +2187,10 @@
<dimen name="volume_dialog_background_square_corner_radius">12dp</dimen>
- <dimen name="volume_dialog_ringer_drawer_margin">@dimen/volume_dialog_buttons_margin</dimen>
<dimen name="volume_dialog_ringer_drawer_button_size">@dimen/volume_dialog_button_size</dimen>
+ <dimen name="volume_dialog_ringer_drawer_buttons_spacing">
+ @dimen/volume_dialog_components_spacing
+ </dimen>
<dimen name="volume_dialog_ringer_drawer_button_icon_radius">10dp</dimen>
<dimen name="volume_dialog_ringer_selected_button_background_radius">20dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b627bdf22a6c..681bd53f1a40 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2551,6 +2551,9 @@
<!-- Label for header of customize QS [CHAR LIMIT=60] -->
<string name="drag_to_rearrange_tiles">Hold and drag to rearrange tiles</string>
+ <!-- Label for placing tiles in edit mode for QS [CHAR LIMIT=60] -->
+ <string name="tap_to_position_tile">Tap to position tile</string>
+
<!-- Label for area where tiles can be dragged in to [CHAR LIMIT=60] -->
<string name="drag_to_remove_tiles">Drag here to remove</string>
@@ -2592,6 +2595,12 @@
<!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_remove_tile_action">remove tile</string>
+ <!-- Accessibility description of action to select the QS tile to place on click. It will read as "Double-tap to toggle placement mode" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_toggle_placement_mode">toggle placement mode</string>
+
+ <!-- Accessibility description of action to toggle the QS tile selection. It will read as "Double-tap to toggle selection" in screen readers [CHAR LIMIT=NONE] -->
+ <string name="accessibility_qs_edit_toggle_selection">toggle selection</string>
+
<!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to the last position" in screen readers [CHAR LIMIT=NONE] -->
<string name="accessibility_qs_edit_tile_add_action">add tile to the last position</string>
@@ -4200,6 +4209,12 @@
All Quick Settings tiles will reset to the device’s original settings
</string>
+
+ <!-- Content of the Reset Tiles dialog in QS Edit mode. [CHAR LIMIT=NONE] -->
+ <string name="demote_explain_text">
+ <xliff:g id="application" example= "Superfast Food Delivery">%1$s</xliff:g> will no longer show Live Updates here. You can change this any time in Settings.
+ </string>
+
<!-- Template that joins disabled message with the label for the voice over. [CHAR LIMIT=NONE] -->
<string name="volume_slider_disabled_message_template"><xliff:g example="Notification" id="stream_name">%1$s</xliff:g>, <xliff:g example="Disabled because ring is muted" id="disabled_message">%2$s</xliff:g></string>
</resources>
diff --git a/packages/SystemUI/shared/biometrics/Android.bp b/packages/SystemUI/shared/biometrics/Android.bp
index 63de81d4a680..208157c69adf 100644
--- a/packages/SystemUI/shared/biometrics/Android.bp
+++ b/packages/SystemUI/shared/biometrics/Android.bp
@@ -14,6 +14,9 @@ android_library {
"src/**/*.java",
"src/**/*.kt",
],
+ static_libs: [
+ "SystemUI-shared-utils",
+ ],
resource_dirs: [
"res",
],
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 5b99a3f16fc2..7aa09cf64405 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -41,6 +41,7 @@ import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.utils.windowmanager.WindowManagerUtils
object Utils {
private const val TAG = "SysUIBiometricUtils"
@@ -117,10 +118,9 @@ object Utils {
@JvmStatic
fun getNavbarInsets(context: Context): Insets {
- val windowManager: WindowManager? = context.getSystemService(WindowManager::class.java)
- val windowMetrics: WindowMetrics? = windowManager?.maximumWindowMetrics
- return windowMetrics?.windowInsets?.getInsets(WindowInsets.Type.navigationBars())
- ?: Insets.NONE
+ val windowManager: WindowManager = WindowManagerUtils.getWindowManager(context)
+ val windowMetrics: WindowMetrics = windowManager.maximumWindowMetrics
+ return windowMetrics.windowInsets.getInsets(WindowInsets.Type.navigationBars())
}
/** Converts `drawable` to a [Bitmap]. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index ea7321627322..b8cd5bec2cbe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -208,4 +208,19 @@ public class PreviewPositionHelper {
}
mMatrix.postTranslate(translateX, translateY);
}
+
+ /**
+ * A factory that returns a new instance of the {@link PreviewPositionHelper}.
+ * <p>{@link PreviewPositionHelper} is a stateful helper, and hence when using it in distinct
+ * scenarios, prefer fetching an object using this factory</p>
+ * <p>Additionally, helpful for injecting mocks in tests</p>
+ */
+ public static class PreviewPositionHelperFactory {
+ /**
+ * Returns a new {@link PreviewPositionHelper} for use in a distinct scenario.
+ */
+ public PreviewPositionHelper create() {
+ return new PreviewPositionHelper();
+ }
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
index c82243934b8b..b1fc560f406b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.java
@@ -37,6 +37,7 @@ import android.view.Surface;
import android.view.WindowManager;
import com.android.systemui.shared.recents.model.Task;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
/* Common code */
public class Utilities {
@@ -152,7 +153,7 @@ public class Utilities {
/** @return whether or not {@param context} represents that of a large screen device or not */
@TargetApi(Build.VERSION_CODES.R)
public static boolean isLargeScreen(Context context) {
- return isLargeScreen(context.getSystemService(WindowManager.class), context.getResources());
+ return isLargeScreen(WindowManagerUtils.getWindowManager(context), context.getResources());
}
/** @return whether or not {@param context} represents that of a large screen device or not */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
index 4db6ab6ea579..570d774b95e9 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButton.java
@@ -16,9 +16,6 @@
package com.android.systemui.shared.rotation;
-import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-
import android.annotation.DimenRes;
import android.annotation.IdRes;
import android.annotation.LayoutRes;
@@ -33,6 +30,7 @@ import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
@@ -40,8 +38,8 @@ import android.widget.FrameLayout;
import androidx.annotation.BoolRes;
import androidx.core.view.OneShotPreDrawListener;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
/**
* Containing logic for the rotation button on the physical left bottom corner of the screen.
@@ -50,7 +48,7 @@ public class FloatingRotationButton implements RotationButton {
private static final int MARGIN_ANIMATION_DURATION_MILLIS = 300;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final ViewGroup mKeyButtonContainer;
private final FloatingRotationButtonView mKeyButtonView;
@@ -91,8 +89,7 @@ public class FloatingRotationButton implements RotationButton {
@DimenRes int taskbarBottomMargin, @DimenRes int buttonDiameter,
@DimenRes int rippleMaxWidth, @BoolRes int floatingRotationBtnPositionLeftResource) {
mContext = context;
- mWindowManager = getViewCaptureAwareWindowManagerInstance(mContext,
- enableViewCaptureTracing());
+ mWindowManager = WindowManagerUtils.getWindowManager(mContext);
mKeyButtonContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(layout, null);
mKeyButtonView = mKeyButtonContainer.findViewById(keyButtonId);
mKeyButtonView.setVisibility(View.VISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index 892851cd7056..8a307145023d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -160,7 +160,7 @@ public interface KeyguardViewController {
/**
* Shows the primary bouncer.
*/
- void showPrimaryBouncer(boolean scrimmed);
+ void showPrimaryBouncer(boolean scrimmed, String reason);
/**
* When the primary bouncer is fully visible or is showing but animation didn't finish yet.
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 19da5de6b531..59ec6923ce91 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -66,7 +66,6 @@ import android.widget.FrameLayout;
import androidx.annotation.VisibleForTesting;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.util.Preconditions;
import com.android.settingslib.Utils;
import com.android.systemui.biometrics.data.repository.FacePropertyRepository;
@@ -167,7 +166,7 @@ public class ScreenDecorations implements
ViewGroup mScreenDecorHwcWindow;
@VisibleForTesting
ScreenDecorHwcLayer mScreenDecorHwcLayer;
- private ViewCaptureAwareWindowManager mWindowManager;
+ private WindowManager mWindowManager;
private int mRotation;
private UserSettingObserver mColorInversionSetting;
private DelayableExecutor mExecutor;
@@ -337,7 +336,7 @@ public class ScreenDecorations implements
FacePropertyRepository facePropertyRepository,
JavaAdapter javaAdapter,
CameraProtectionLoader cameraProtectionLoader,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ WindowManager windowManager,
@ScreenDecorationsThread Handler handler,
@ScreenDecorationsThread DelayableExecutor executor) {
mContext = context;
@@ -353,7 +352,7 @@ public class ScreenDecorations implements
mLogger = logger;
mFacePropertyRepository = facePropertyRepository;
mJavaAdapter = javaAdapter;
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mHandler = handler;
mExecutor = executor;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 115242eb13aa..375137c67f7c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -44,7 +44,6 @@ import android.window.InputTransferToken;
import androidx.annotation.NonNull;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.dagger.SysUISingleton;
@@ -54,6 +53,7 @@ import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import java.io.PrintWriter;
import java.util.concurrent.Executor;
@@ -97,20 +97,19 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
private final WindowMagnifierCallback mWindowMagnifierCallback;
private final SysUiState mSysUiState;
private final SecureSettings mSecureSettings;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private final WindowManagerProvider mWindowManagerProvider;
WindowMagnificationControllerSupplier(Context context, Handler handler,
WindowMagnifierCallback windowMagnifierCallback,
DisplayManager displayManager, SysUiState sysUiState,
- SecureSettings secureSettings,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ SecureSettings secureSettings, WindowManagerProvider windowManagerProvider) {
super(displayManager);
mContext = context;
mHandler = handler;
mWindowMagnifierCallback = windowMagnifierCallback;
mSysUiState = sysUiState;
mSecureSettings = secureSettings;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
@@ -118,6 +117,8 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
final Context windowContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_OVERLAY,
/* options */ null);
+ final WindowManager windowManager = mWindowManagerProvider
+ .getWindowManager(windowContext);
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
Supplier<SurfaceControlViewHost> scvhSupplier = () ->
@@ -133,7 +134,8 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
mWindowMagnifierCallback,
mSysUiState,
mSecureSettings,
- scvhSupplier);
+ scvhSupplier,
+ windowManager);
}
}
@@ -148,17 +150,20 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
private final Executor mExecutor;
private final DisplayManager mDisplayManager;
private final IWindowManager mIWindowManager;
+ private final WindowManagerProvider mWindowManagerProvider;
FullscreenMagnificationControllerSupplier(Context context,
DisplayManager displayManager,
Handler handler,
- Executor executor, IWindowManager iWindowManager) {
+ Executor executor, IWindowManager iWindowManager,
+ WindowManagerProvider windowManagerProvider) {
super(displayManager);
mContext = context;
mHandler = handler;
mExecutor = executor;
mDisplayManager = displayManager;
mIWindowManager = iWindowManager;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
@@ -174,7 +179,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
mExecutor,
mDisplayManager,
windowContext.getSystemService(AccessibilityManager.class),
- windowContext.getSystemService(WindowManager.class),
+ mWindowManagerProvider.getWindowManager(windowContext),
mIWindowManager,
scvhSupplier);
}
@@ -190,31 +195,32 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
private final Context mContext;
private final MagnificationSettingsController.Callback mSettingsControllerCallback;
private final SecureSettings mSecureSettings;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private final WindowManagerProvider mWindowManagerProvider;
SettingsSupplier(Context context,
MagnificationSettingsController.Callback settingsControllerCallback,
DisplayManager displayManager,
- SecureSettings secureSettings,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ SecureSettings secureSettings, WindowManagerProvider windowManagerProvider) {
super(displayManager);
mContext = context;
mSettingsControllerCallback = settingsControllerCallback;
mSecureSettings = secureSettings;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
protected MagnificationSettingsController createInstance(Display display) {
final Context windowContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_OVERLAY, /* options */ null);
+ final WindowManager windowManager = mWindowManagerProvider
+ .getWindowManager(windowContext);
windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
return new MagnificationSettingsController(
windowContext,
new SfVsyncFrameCallbackProvider(),
mSettingsControllerCallback,
mSecureSettings,
- mViewCaptureAwareWindowManager);
+ windowManager);
}
}
@@ -229,11 +235,11 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
SecureSettings secureSettings, DisplayTracker displayTracker,
DisplayManager displayManager, AccessibilityLogger a11yLogger,
IWindowManager iWindowManager, AccessibilityManager accessibilityManager,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManagerProvider windowManagerProvider) {
this(context, mainHandler.getLooper(), executor, commandQueue,
modeSwitchesController, sysUiState, launcherProxyService, secureSettings,
displayTracker, displayManager, a11yLogger, iWindowManager, accessibilityManager,
- viewCaptureAwareWindowManager);
+ windowManagerProvider);
}
@VisibleForTesting
@@ -244,7 +250,7 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
DisplayManager displayManager, AccessibilityLogger a11yLogger,
IWindowManager iWindowManager,
AccessibilityManager accessibilityManager,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManagerProvider windowManagerProvider) {
mHandler = new Handler(looper) {
@Override
public void handleMessage(@NonNull Message msg) {
@@ -263,12 +269,13 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
mA11yLogger = a11yLogger;
mWindowMagnificationControllerSupplier = new WindowMagnificationControllerSupplier(context,
mHandler, mWindowMagnifierCallback,
- displayManager, sysUiState, secureSettings, viewCaptureAwareWindowManager);
+ displayManager, sysUiState, secureSettings, windowManagerProvider);
mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
- context, displayManager, mHandler, mExecutor, iWindowManager);
+ context, displayManager, mHandler, mExecutor, iWindowManager,
+ windowManagerProvider);
mMagnificationSettingsSupplier = new SettingsSupplier(context,
mMagnificationSettingsControllerCallback, displayManager, secureSettings,
- viewCaptureAwareWindowManager);
+ windowManagerProvider);
mModeSwitchesController.setClickListenerDelegate(
displayId -> mHandler.post(() -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
index 4723ab958f86..9eb01de239bc 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationModeSwitch.java
@@ -46,7 +46,6 @@ import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.ImageView;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.res.R;
@@ -77,7 +76,6 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final ImageView mImageView;
private final Runnable mWindowInsetChangeRunnable;
private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider;
@@ -101,21 +99,20 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
void onClick(int displayId);
}
- MagnificationModeSwitch(@UiContext Context context, ClickListener clickListener,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
- this(context, createView(context), new SfVsyncFrameCallbackProvider(), clickListener,
- viewCaptureAwareWindowManager);
+ MagnificationModeSwitch(@UiContext Context context, WindowManager windowManager,
+ ClickListener clickListener) {
+ this(context, windowManager, createView(context), new SfVsyncFrameCallbackProvider(),
+ clickListener);
}
@VisibleForTesting
- MagnificationModeSwitch(Context context, @NonNull ImageView imageView,
- SfVsyncFrameCallbackProvider sfVsyncFrameProvider, ClickListener clickListener,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ MagnificationModeSwitch(Context context, WindowManager windowManager,
+ @NonNull ImageView imageView, SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
+ ClickListener clickListener) {
mContext = context;
mConfiguration = new Configuration(context.getResources().getConfiguration());
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mWindowManager = mContext.getSystemService(WindowManager.class);
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mClickListener = clickListener;
mParams = createLayoutParams(context);
@@ -282,7 +279,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
mImageView.animate().cancel();
mIsFadeOutAnimating = false;
mImageView.setAlpha(0f);
- mViewCaptureAwareWindowManager.removeView(mImageView);
+ mWindowManager.removeView(mImageView);
mContext.unregisterComponentCallbacks(this);
mIsVisible = false;
}
@@ -316,7 +313,7 @@ class MagnificationModeSwitch implements MagnificationGestureDetector.OnGestureL
mParams.y = mDraggableWindowBounds.bottom;
mToLeftScreenEdge = false;
}
- mViewCaptureAwareWindowManager.addView(mImageView, mParams);
+ mWindowManager.addView(mImageView, mParams);
// Exclude magnification switch button from system gesture area.
setSystemGestureExclusion();
mIsVisible = true;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
index fc7535a712e3..2d5dc8d23383 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationSettingsController.java
@@ -26,7 +26,6 @@ import android.content.res.Configuration;
import android.util.Range;
import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.accessibility.common.MagnificationConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
@@ -62,9 +61,9 @@ public class MagnificationSettingsController implements ComponentCallbacks {
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
@NonNull Callback settingsControllerCallback,
SecureSettings secureSettings,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
- this(context, sfVsyncFrameProvider, settingsControllerCallback, secureSettings, null,
- viewCaptureAwareWindowManager);
+ WindowManager windowManager) {
+ this(context, sfVsyncFrameProvider, settingsControllerCallback, secureSettings,
+ windowManager, null);
}
@VisibleForTesting
@@ -73,8 +72,8 @@ public class MagnificationSettingsController implements ComponentCallbacks {
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
@NonNull Callback settingsControllerCallback,
SecureSettings secureSettings,
- WindowMagnificationSettings windowMagnificationSettings,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager,
+ WindowMagnificationSettings windowMagnificationSettings) {
mContext = context.createWindowContext(
context.getDisplay(),
WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
@@ -88,7 +87,7 @@ public class MagnificationSettingsController implements ComponentCallbacks {
} else {
mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
mWindowMagnificationSettingsCallback,
- sfVsyncFrameProvider, secureSettings, viewCaptureAwareWindowManager);
+ sfVsyncFrameProvider, secureSettings, windowManager);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
index eb4de6837d41..7f3a869d8222 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MirrorWindowControl.java
@@ -18,9 +18,6 @@ package com.android.systemui.accessibility;
import static android.view.WindowManager.LayoutParams;
-import static com.android.app.viewcapture.ViewCaptureFactory.getViewCaptureAwareWindowManagerInstance;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -32,8 +29,8 @@ import android.util.MathUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.res.R;
/**
@@ -73,12 +70,11 @@ public abstract class MirrorWindowControl {
* @see #setDefaultPosition(LayoutParams)
*/
private final Point mControlPosition = new Point();
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
- MirrorWindowControl(Context context) {
+ MirrorWindowControl(Context context, WindowManager windowManager) {
mContext = context;
- mWindowManager = getViewCaptureAwareWindowManagerInstance(mContext,
- enableViewCaptureTracing());
+ mWindowManager = windowManager;
}
public void setWindowDelegate(@Nullable MirrorWindowDelegate windowDelegate) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
index 53827e65344a..7d9f8674457c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/ModeSwitchesController.java
@@ -24,10 +24,11 @@ import android.annotation.MainThread;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.view.Display;
+import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import javax.inject.Inject;
@@ -49,9 +50,9 @@ public class ModeSwitchesController implements ClickListener {
@Inject
public ModeSwitchesController(Context context, DisplayManager displayManager,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManagerProvider windowManagerProvider) {
mSwitchSupplier = new SwitchSupplier(context, displayManager, this::onClick,
- viewCaptureAwareWindowManager);
+ windowManagerProvider);
}
@VisibleForTesting
@@ -118,7 +119,7 @@ public class ModeSwitchesController implements ClickListener {
private final Context mContext;
private final ClickListener mClickListener;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private final WindowManagerProvider mWindowManagerProvider;
/**
* Supplies the switch for the given display.
@@ -128,20 +129,20 @@ public class ModeSwitchesController implements ClickListener {
* @param clickListener The callback that will run when the switch is clicked
*/
SwitchSupplier(Context context, DisplayManager displayManager,
- ClickListener clickListener,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ ClickListener clickListener, WindowManagerProvider windowManagerProvider) {
super(displayManager);
mContext = context;
mClickListener = clickListener;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
protected MagnificationModeSwitch createInstance(Display display) {
final Context uiContext = mContext.createWindowContext(display,
TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, /* options */ null);
- return new MagnificationModeSwitch(uiContext, mClickListener,
- mViewCaptureAwareWindowManager);
+ final WindowManager uiWindowManager = mWindowManagerProvider
+ .getWindowManager(uiContext);
+ return new MagnificationModeSwitch(uiContext, uiWindowManager, mClickListener);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
index bc469eed7359..3cde033bf56a 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SimpleMirrorWindowControl.java
@@ -27,6 +27,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowManager;
import com.android.systemui.res.R;
@@ -48,8 +49,8 @@ class SimpleMirrorWindowControl extends MirrorWindowControl implements View.OnCl
private final PointF mLastDrag = new PointF();
private final Handler mHandler;
- SimpleMirrorWindowControl(Context context, Handler handler) {
- super(context);
+ SimpleMirrorWindowControl(Context context, Handler handler, WindowManager windowManager) {
+ super(context, windowManager);
mHandler = handler;
final Resources resource = context.getResources();
mMoveFrameAmountShort = resource.getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 8734d05bc894..9cd77e790e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -249,7 +249,8 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
@NonNull WindowMagnifierCallback callback,
SysUiState sysUiState,
SecureSettings secureSettings,
- Supplier<SurfaceControlViewHost> scvhSupplier) {
+ Supplier<SurfaceControlViewHost> scvhSupplier,
+ WindowManager windowManager) {
mContext = context;
mHandler = handler;
mAnimationController = animationController;
@@ -265,7 +266,7 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
mDisplayId = mContext.getDisplayId();
mRotation = display.getRotation();
- mWm = context.getSystemService(WindowManager.class);
+ mWm = windowManager;
mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds());
mResources = mContext.getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
index 3b6f8f87a1a8..bd4b6420bf37 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java
@@ -56,7 +56,6 @@ import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Switch;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
@@ -75,7 +74,6 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
private final Context mContext;
private final AccessibilityManager mAccessibilityManager;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final SecureSettings mSecureSettings;
private final Runnable mWindowInsetChangeRunnable;
@@ -137,11 +135,10 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
@VisibleForTesting
WindowMagnificationSettings(Context context, WindowMagnificationSettingsCallback callback,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider, SecureSettings secureSettings,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager) {
mContext = context;
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
- mWindowManager = mContext.getSystemService(WindowManager.class);
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mSfVsyncFrameProvider = sfVsyncFrameProvider;
mCallback = callback;
mSecureSettings = secureSettings;
@@ -324,7 +321,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
// Unregister observer before removing view
mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver);
- mViewCaptureAwareWindowManager.removeView(mSettingView);
+ mWindowManager.removeView(mSettingView);
mIsVisible = false;
if (resetPosition) {
mParams.x = 0;
@@ -382,7 +379,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest
mParams.y = mDraggableWindowBounds.bottom;
}
- mViewCaptureAwareWindowManager.addView(mSettingView, mParams);
+ mWindowManager.addView(mSettingView, mParams);
mSecureSettings.registerContentObserverForUserSync(
Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index f8e4bda15d01..ef42837ba776 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -31,7 +31,6 @@ import android.view.accessibility.IUserInitializationCompleteCallback;
import androidx.annotation.MainThread;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -60,7 +59,6 @@ public class AccessibilityFloatingMenuController implements
private final Context mContext;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final DisplayManager mDisplayManager;
private final AccessibilityManager mAccessibilityManager;
private final HearingAidDeviceManager mHearingAidDeviceManager;
@@ -105,7 +103,6 @@ public class AccessibilityFloatingMenuController implements
@Inject
public AccessibilityFloatingMenuController(Context context,
WindowManager windowManager,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
DisplayManager displayManager,
AccessibilityManager accessibilityManager,
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
@@ -118,7 +115,6 @@ public class AccessibilityFloatingMenuController implements
@Main Handler handler) {
mContext = context;
mWindowManager = windowManager;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
mDisplayManager = displayManager;
mAccessibilityManager = accessibilityManager;
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
@@ -205,8 +201,8 @@ public class AccessibilityFloatingMenuController implements
final Context windowContext = mContext.createWindowContext(defaultDisplay,
TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
- mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
- mNavigationModeController, mHearingAidDeviceManager);
+ mAccessibilityManager, mSecureSettings, mNavigationModeController,
+ mHearingAidDeviceManager);
}
mFloatingMenu.show();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
index 13c1a450832f..52e69efdbc19 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractView.kt
@@ -48,7 +48,7 @@ import com.android.wm.shell.shared.bubbles.DismissView
*
* @note [setup] method should be called after initialisation
*/
-class DragToInteractView(context: Context) : FrameLayout(context) {
+class DragToInteractView(context: Context, windowManager: WindowManager) : FrameLayout(context) {
/**
* The configuration is used to provide module specific resource ids
*
@@ -86,8 +86,7 @@ class DragToInteractView(context: Context) : FrameLayout(context) {
private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY)
private val INTERACT_SCRIM_FADE_MS = 200L
- private var wm: WindowManager =
- context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ private var wm: WindowManager = windowManager
private var gradientDrawable: GradientDrawable? = null
private val GRADIENT_ALPHA: IntProperty<GradientDrawable> =
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 81095220b4a6..8fb260c2df36 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -212,7 +212,7 @@ class MenuViewLayer extends FrameLayout implements
mMenuAnimationController = mMenuView.getMenuAnimationController();
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
- mDragToInteractView = new DragToInteractView(context);
+ mDragToInteractView = new DragToInteractView(context, windowManager);
DismissViewUtils.setup(mDismissView);
mDismissView.getCircle().setId(R.id.action_remove_menu);
mNotificationFactory = new MenuNotificationFactory(context);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 102efcf7badd..7bf7e23b5df5 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -25,7 +25,6 @@ import android.graphics.PixelFormat;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.settingslib.bluetooth.HearingAidDeviceManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.util.settings.SecureSettings;
@@ -35,16 +34,15 @@ import com.android.systemui.util.settings.SecureSettings;
* of {@link IAccessibilityFloatingMenu}.
*/
class MenuViewLayerController implements IAccessibilityFloatingMenu {
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final MenuViewLayer mMenuViewLayer;
private boolean mIsShowing;
MenuViewLayerController(Context context, WindowManager windowManager,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
AccessibilityManager accessibilityManager, SecureSettings secureSettings,
NavigationModeController navigationModeController,
HearingAidDeviceManager hearingAidDeviceManager) {
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
MenuViewModel menuViewModel = new MenuViewModel(
context, accessibilityManager, secureSettings, hearingAidDeviceManager);
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
index 6f2dd799c409..633c13e9e94d 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/scrim/BouncerScrimController.java
@@ -34,7 +34,7 @@ public class BouncerScrimController implements ScrimController {
@Override
public void show(boolean scrimmed) {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(scrimmed, "BouncerScrimController#show");
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index f4e2b82f1773..14ce2cf60919 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -33,7 +33,6 @@ import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.res.R;
/**
@@ -41,17 +40,16 @@ import com.android.systemui.res.R;
*/
public class AssistDisclosure {
private final Context mContext;
- private final ViewCaptureAwareWindowManager mWm;
+ private final WindowManager mWm;
private final Handler mHandler;
private AssistDisclosureView mView;
private boolean mViewAdded;
- public AssistDisclosure(Context context, Handler handler,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ public AssistDisclosure(Context context, Handler handler, WindowManager windowManager) {
mContext = context;
mHandler = handler;
- mWm = viewCaptureAwareWindowManager;
+ mWm = windowManager;
}
public void postShow() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index 2d44d401b0b0..75ec8dfd881e 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -24,8 +24,8 @@ import android.provider.Settings;
import android.service.voice.VisualQueryAttentionResult;
import android.service.voice.VoiceInteractionSession;
import android.util.Log;
+import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.app.AssistUtils;
import com.android.internal.app.IVisualQueryDetectionAttentionListener;
import com.android.internal.app.IVisualQueryRecognitionStatusListener;
@@ -199,12 +199,12 @@ public class AssistManager {
SelectedUserInteractor selectedUserInteractor,
ActivityManager activityManager,
AssistInteractor interactor,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager) {
mContext = context;
mDeviceProvisionedController = controller;
mCommandQueue = commandQueue;
mAssistUtils = assistUtils;
- mAssistDisclosure = new AssistDisclosure(context, uiHandler, viewCaptureAwareWindowManager);
+ mAssistDisclosure = new AssistDisclosure(context, uiHandler, windowManager);
mLauncherProxyService = launcherProxyService;
mPhoneStateMonitor = phoneStateMonitor;
mAssistLogger = assistLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
index 6e257442d139..56273eb9a2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/ui/DefaultUiController.java
@@ -33,7 +33,6 @@ import android.view.WindowManager;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.assist.AssistLogger;
@@ -67,7 +66,7 @@ public class DefaultUiController implements AssistManager.UiController {
protected InvocationLightsView mInvocationLightsView;
protected final AssistLogger mAssistLogger;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final MetricsLogger mMetricsLogger;
private final Lazy<AssistManager> mAssistManagerLazy;
private final WindowManager.LayoutParams mLayoutParams;
@@ -81,12 +80,12 @@ public class DefaultUiController implements AssistManager.UiController {
@Inject
public DefaultUiController(Context context, AssistLogger assistLogger,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
- MetricsLogger metricsLogger, Lazy<AssistManager> assistManagerLazy,
+ WindowManager windowManager, MetricsLogger metricsLogger,
+ Lazy<AssistManager> assistManagerLazy,
NavigationBarController navigationBarController) {
mAssistLogger = assistLogger;
mRoot = new FrameLayout(context);
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mMetricsLogger = metricsLogger;
mAssistManagerLazy = assistManagerLazy;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b8e95ee1dbf0..7bb08edd4773 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -20,7 +20,6 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import android.animation.Animator;
import android.annotation.IntDef;
@@ -57,8 +56,6 @@ import android.window.OnBackInvokedDispatcher;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.android.app.animation.Interpolators;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.widget.LockPatternUtils;
@@ -78,11 +75,10 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
import com.google.android.msdl.domain.MSDLPlayer;
-import kotlin.Lazy;
-
import kotlinx.coroutines.CoroutineScope;
import java.io.PrintWriter;
@@ -132,7 +128,7 @@ public class AuthContainerView extends LinearLayout
private final Config mConfig;
private final int mEffectiveUserId;
private final IBinder mWindowToken = new Binder();
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
@Nullable private final AuthContextPlugins mAuthContextPlugins;
private final Interpolator mLinearOutSlowIn;
private final LockPatternUtils mLockPatternUtils;
@@ -298,16 +294,13 @@ public class AuthContainerView extends LinearLayout
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull @Background DelayableExecutor bgExecutor,
@NonNull VibratorHelper vibratorHelper,
- Lazy<ViewCapture> lazyViewCapture,
@NonNull MSDLPlayer msdlPlayer) {
super(config.mContext);
mConfig = config;
mLockPatternUtils = lockPatternUtils;
mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId);
- WindowManager wm = getContext().getSystemService(WindowManager.class);
- mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
- enableViewCaptureTracing());
+ mWindowManager = WindowManagerUtils.getWindowManager(getContext());
mAuthContextPlugins = authContextPlugins;
mWakefulnessLifecycle = wakefulnessLifecycle;
mApplicationCoroutineScope = applicationCoroutineScope;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 68a282018ba4..f2c071f14466 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -22,7 +22,6 @@ import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_REAR
import static android.view.Display.INVALID_DISPLAY;
import static com.android.systemui.Flags.contAuthPlugin;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -67,7 +66,6 @@ import android.view.DisplayInfo;
import android.view.MotionEvent;
import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCapture;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.InteractionJankMonitor;
@@ -93,6 +91,7 @@ import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.Execution;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.google.android.msdl.domain.MSDLPlayer;
@@ -194,7 +193,7 @@ public class AuthController implements
@NonNull private final VibratorHelper mVibratorHelper;
@NonNull private final MSDLPlayer mMSDLPlayer;
- private final kotlin.Lazy<ViewCapture> mLazyViewCapture;
+ private final WindowManagerProvider mWindowManagerProvider;
@VisibleForTesting
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@@ -693,8 +692,8 @@ public class AuthController implements
@NonNull UdfpsUtils udfpsUtils,
@NonNull VibratorHelper vibratorHelper,
@NonNull KeyguardManager keyguardManager,
- Lazy<ViewCapture> daggerLazyViewCapture,
- @NonNull MSDLPlayer msdlPlayer) {
+ @NonNull MSDLPlayer msdlPlayer,
+ WindowManagerProvider windowManagerProvider) {
mContext = context;
mExecution = execution;
mUserManager = userManager;
@@ -755,7 +754,7 @@ public class AuthController implements
context.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class);
- mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
+ mWindowManagerProvider = windowManagerProvider;
}
// TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
@@ -1278,7 +1277,7 @@ public class AuthController implements
Log.e(TAG, "unable to get Display for user=" + userId);
return null;
}
- return mContext.createDisplayContext(display).getSystemService(WindowManager.class);
+ return mWindowManagerProvider.getWindowManager(mContext.createDisplayContext(display));
}
private void onDialogDismissed(@BiometricPrompt.DismissedReason int reason) {
@@ -1337,8 +1336,7 @@ public class AuthController implements
return new AuthContainerView(config, mApplicationCoroutineScope, mFpProps, mFaceProps,
wakefulnessLifecycle, userManager, mContextPlugins, lockPatternUtils,
mInteractionJankMonitor, mPromptSelectorInteractor, viewModel,
- mCredentialViewModelProvider, bgExecutor, mVibratorHelper,
- mLazyViewCapture, mMSDLPlayer);
+ mCredentialViewModelProvider, bgExecutor, mVibratorHelper, mMSDLPlayer);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index dfe8eb28b2a6..b16c416fb9df 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -57,12 +57,12 @@ import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
+import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
@@ -145,7 +145,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
private final Execution mExecution;
private final FingerprintManager mFingerprintManager;
@NonNull private final LayoutInflater mInflater;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final DelayableExecutor mFgExecutor;
@NonNull private final Executor mBiometricExecutor;
@NonNull private final StatusBarStateController mStatusBarStateController;
@@ -659,7 +659,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull Execution execution,
@NonNull @ShadeDisplayAware LayoutInflater inflater,
@Nullable FingerprintManager fingerprintManager,
- @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @NonNull @Main WindowManager windowManager,
@NonNull StatusBarStateController statusBarStateController,
@Main DelayableExecutor fgExecutor,
@NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -705,7 +705,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
// The fingerprint manager is queried for UDFPS before this class is constructed, so the
// fingerprint manager should never be null.
mFingerprintManager = checkNotNull(fingerprintManager);
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mFgExecutor = fgExecutor;
mStatusBarStateController = statusBarStateController;
mKeyguardStateController = keyguardStateController;
@@ -880,7 +880,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
return;
}
- mKeyguardViewManager.showPrimaryBouncer(true);
+ mKeyguardViewManager.showPrimaryBouncer(true, "UdfpsController#onAodInterrupt");
// play the same haptic as the DeviceEntryIcon longpress
if (mOverlay != null && mOverlay.getTouchOverlay() != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index bdf58275effa..61b670715572 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -42,7 +42,6 @@ import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
@@ -88,7 +87,7 @@ class UdfpsControllerOverlay
constructor(
private val context: Context,
private val inflater: LayoutInflater,
- private val windowManager: ViewCaptureAwareWindowManager,
+ private val windowManager: WindowManager,
private val accessibilityManager: AccessibilityManager,
private val statusBarStateController: StatusBarStateController,
private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 54c52b533da4..3b22e13f29a2 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -397,7 +397,7 @@ object BiometricViewBinder {
// Talkback directional guidance
udfpsGuidanceView.setOnHoverListener { _, event ->
launch {
- viewModel.onAnnounceAccessibilityHint(
+ viewModel.onUpdateAccessibilityHint(
event,
accessibilityManager.isTouchExplorationEnabled,
)
@@ -406,7 +406,9 @@ object BiometricViewBinder {
}
launch {
viewModel.accessibilityHint.collect { message ->
- if (message.isNotBlank()) view.announceForAccessibility(message)
+ if (message.isNotBlank()) {
+ udfpsGuidanceView.contentDescription = message
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 02c378417f3d..fcc0121e8f93 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -27,7 +27,6 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewOutlineProvider
import android.view.WindowInsets
-import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
@@ -48,6 +47,7 @@ import com.android.systemui.biometrics.ui.viewmodel.isSmall
import com.android.systemui.biometrics.ui.viewmodel.isTop
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.res.R
+import com.android.systemui.utils.windowmanager.WindowManagerUtils
import kotlin.math.abs
import kotlinx.coroutines.flow.combine
import com.android.app.tracing.coroutines.launchTraced as launch
@@ -66,7 +66,7 @@ object BiometricViewSizeBinder {
viewsToHideWhenSmall: List<View>,
jankListener: BiometricJankListener,
) {
- val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+ val windowManager = WindowManagerUtils.getWindowManager(view.context)
val accessibilityManager =
requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 0902d19b6787..4e17a2658ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -886,7 +886,7 @@ constructor(
}
/** Sets the message used for UDFPS directional guidance */
- suspend fun onAnnounceAccessibilityHint(
+ suspend fun onUpdateAccessibilityHint(
event: MotionEvent,
touchExplorationEnabled: Boolean,
): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 0c6d7920d7f3..48e08fcd90c5 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -135,7 +135,7 @@ constructor(
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
- fun show(isScrimmed: Boolean): Boolean {
+ fun show(isScrimmed: Boolean, reason: String): Boolean {
// When the scene container framework is enabled, instead of calling this, call
// SceneInteractor#changeScene(Scenes.Bouncer, ...).
SceneContainerFlag.assertInLegacyMode()
@@ -176,6 +176,7 @@ constructor(
return false
}
+ Log.i(TAG, "Show primary bouncer requested, reason: $reason")
repository.setPrimaryShowingSoon(true)
if (usePrimaryBouncerPassiveAuthDelay()) {
Log.d(TAG, "delay bouncer, passive auth may succeed")
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 7d518f4f7e78..718ef51aa161 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -23,20 +23,19 @@ import android.os.SystemProperties
import android.view.Surface
import android.view.View
import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.Utils
+import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.res.R
+import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.surfaceeffects.ripple.RippleView
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
@@ -58,7 +57,6 @@ class WiredChargingRippleController @Inject constructor(
featureFlags: FeatureFlags,
private val context: Context,
private val windowManager: WindowManager,
- private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger
) {
@@ -163,12 +161,12 @@ class WiredChargingRippleController @Inject constructor(
override fun onViewAttachedToWindow(view: View) {
layoutRipple()
rippleView.startRipple(Runnable {
- viewCaptureAwareWindowManager.removeView(rippleView)
+ windowManager.removeView(rippleView)
})
rippleView.removeOnAttachStateChangeListener(this)
}
})
- viewCaptureAwareWindowManager.addView(rippleView, windowLayoutParams)
+ windowManager.addView(rippleView, windowLayoutParams)
uiEventLogger.log(WiredChargingRippleEvent.CHARGING_RIPPLE_PLAYED)
}
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
index e5e9c4685264..4ca55400561e 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingAnimation.java
@@ -28,10 +28,10 @@ import android.util.Slog;
import android.view.Gravity;
import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.surfaceeffects.ripple.RippleShader.RippleShape;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
/**
* A WirelessChargingAnimation is a view containing view + animation for wireless charging.
@@ -60,11 +60,11 @@ public class WirelessChargingAnimation {
*/
private WirelessChargingAnimation(@NonNull Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback, boolean isDozing,
- RippleShape rippleShape, UiEventLogger uiEventLogger,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ RippleShape rippleShape, UiEventLogger uiEventLogger, WindowManager windowManager,
+ WindowManagerProvider windowManagerProvider) {
mCurrentWirelessChargingView = new WirelessChargingView(context, looper,
transmittingBatteryLevel, batteryLevel, callback, isDozing,
- rippleShape, uiEventLogger, viewCaptureAwareWindowManager);
+ rippleShape, uiEventLogger, windowManager, windowManagerProvider);
}
/**
@@ -75,11 +75,11 @@ public class WirelessChargingAnimation {
public static WirelessChargingAnimation makeWirelessChargingAnimation(@NonNull Context context,
@Nullable Looper looper, int transmittingBatteryLevel, int batteryLevel,
Callback callback, boolean isDozing, RippleShape rippleShape,
- UiEventLogger uiEventLogger,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ UiEventLogger uiEventLogger, WindowManager windowManager,
+ WindowManagerProvider windowManagerProvider) {
return new WirelessChargingAnimation(context, looper, transmittingBatteryLevel,
- batteryLevel, callback, isDozing, rippleShape, uiEventLogger,
- viewCaptureAwareWindowManager);
+ batteryLevel, callback, isDozing, rippleShape, uiEventLogger, windowManager,
+ windowManagerProvider);
}
/**
@@ -88,10 +88,10 @@ public class WirelessChargingAnimation {
*/
public static WirelessChargingAnimation makeChargingAnimationWithNoBatteryLevel(
@NonNull Context context, RippleShape rippleShape, UiEventLogger uiEventLogger,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager, WindowManagerProvider windowManagerProvider) {
return makeWirelessChargingAnimation(context, null,
UNKNOWN_BATTERY_LEVEL, UNKNOWN_BATTERY_LEVEL, null, false,
- rippleShape, uiEventLogger, viewCaptureAwareWindowManager);
+ rippleShape, uiEventLogger, windowManager, windowManagerProvider);
}
/**
@@ -123,19 +123,21 @@ public class WirelessChargingAnimation {
private int mGravity;
private WirelessChargingLayout mView;
private WirelessChargingLayout mNextView;
- private ViewCaptureAwareWindowManager mWM;
+ private WindowManager mWM;
private Callback mCallback;
+ private WindowManagerProvider mWindowManagerProvider;
public WirelessChargingView(Context context, @Nullable Looper looper,
int transmittingBatteryLevel, int batteryLevel, Callback callback,
boolean isDozing, RippleShape rippleShape, UiEventLogger uiEventLogger,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager, WindowManagerProvider windowManagerProvider) {
mCallback = callback;
mNextView = new WirelessChargingLayout(context, transmittingBatteryLevel, batteryLevel,
isDozing, rippleShape);
mGravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER;
mUiEventLogger = uiEventLogger;
- mWM = viewCaptureAwareWindowManager;
+ mWM = windowManager;
+ mWindowManagerProvider = windowManagerProvider;
final WindowManager.LayoutParams params = mParams;
params.height = WindowManager.LayoutParams.MATCH_PARENT;
@@ -207,6 +209,7 @@ public class WirelessChargingAnimation {
if (context == null) {
context = mView.getContext();
}
+ mWM = mWindowManagerProvider.getWindowManager(context);
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = DURATION;
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
index dc3b50c93298..059ea3271235 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayWindow.java
@@ -27,7 +27,6 @@ import android.view.Window;
import android.view.WindowInsets;
import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.PhoneWindow;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
import com.android.systemui.screenshot.FloatingWindowUtil;
@@ -45,7 +44,6 @@ public class ClipboardOverlayWindow extends PhoneWindow
private final Context mContext;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final WindowManager.LayoutParams mWindowLayoutParams;
private boolean mKeyboardVisible;
@@ -55,7 +53,6 @@ public class ClipboardOverlayWindow extends PhoneWindow
@Inject
ClipboardOverlayWindow(@OverlayWindowContext Context context,
- @OverlayWindowContext ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
@OverlayWindowContext WindowManager windowManager) {
super(context);
mContext = context;
@@ -66,10 +63,9 @@ public class ClipboardOverlayWindow extends PhoneWindow
requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS);
setBackgroundDrawableResource(android.R.color.transparent);
mWindowManager = windowManager;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
mWindowLayoutParams = FloatingWindowUtil.getFloatingWindowParams();
mWindowLayoutParams.setTitle("ClipboardOverlay");
- setWindowManager(windowManager, null, null);
+ setWindowManager(mWindowManager, null, null);
setWindowFocusable(false);
}
@@ -86,12 +82,10 @@ public class ClipboardOverlayWindow extends PhoneWindow
attach();
withWindowAttached(() -> {
- WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets();
+ WindowInsets currentInsets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
mKeyboardVisible = currentInsets.isVisible(WindowInsets.Type.ime());
peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(() -> {
- WindowInsets insets = mWindowManager.getCurrentWindowMetrics()
- .getWindowInsets();
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
if (keyboardVisible != mKeyboardVisible) {
mKeyboardVisible = keyboardVisible;
@@ -112,7 +106,7 @@ public class ClipboardOverlayWindow extends PhoneWindow
void remove() {
final View decorView = peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
- mViewCaptureAwareWindowManager.removeViewImmediate(decorView);
+ mWindowManager.removeViewImmediate(decorView);
}
}
@@ -146,7 +140,7 @@ public class ClipboardOverlayWindow extends PhoneWindow
if (decorView.isAttachedToWindow()) {
return;
}
- mViewCaptureAwareWindowManager.addView(decorView, mWindowLayoutParams);
+ mWindowManager.addView(decorView, mWindowLayoutParams);
decorView.requestApplyInsets();
}
@@ -167,7 +161,7 @@ public class ClipboardOverlayWindow extends PhoneWindow
}
final View decorView = peekDecorView();
if (decorView != null && decorView.isAttachedToWindow()) {
- mViewCaptureAwareWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
+ mWindowManager.updateViewLayout(decorView, mWindowLayoutParams);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
index c86a84b17efe..7a60cce63a33 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayModule.java
@@ -19,9 +19,7 @@ package com.android.systemui.clipboardoverlay.dagger;
import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
import static com.android.systemui.Flags.clipboardOverlayMultiuser;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.Flags.usePreferredImageEditor;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -31,8 +29,6 @@ import android.view.Display;
import android.view.LayoutInflater;
import android.view.WindowManager;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.clipboardoverlay.ActionIntentCreator;
import com.android.systemui.clipboardoverlay.ClipboardOverlayView;
import com.android.systemui.clipboardoverlay.DefaultIntentCreator;
@@ -40,6 +36,7 @@ import com.android.systemui.clipboardoverlay.IntentCreator;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import dagger.Lazy;
import dagger.Module;
@@ -89,21 +86,9 @@ public interface ClipboardOverlayModule {
*/
@Provides
@OverlayWindowContext
- static WindowManager provideWindowManager(@OverlayWindowContext Context context) {
- return context.getSystemService(WindowManager.class);
- }
-
- /**
- *
- */
- @Provides
- @OverlayWindowContext
- static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
- @OverlayWindowContext WindowManager windowManager,
- Lazy<ViewCapture> daggerLazyViewCapture) {
- return new ViewCaptureAwareWindowManager(windowManager,
- /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
- /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+ static WindowManager provideWindowManager(@OverlayWindowContext Context context,
+ WindowManagerProvider windowManagerProvider) {
+ return windowManagerProvider.getWindowManager(context);
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt b/packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt
new file mode 100644
index 000000000000..078ea569a63c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/compose/gestures/EagerTap.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.common.ui.compose.gestures
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Detects taps and double taps without waiting for the double tap minimum delay in between
+ *
+ * Using [detectTapGestures] with both a single tap and a double tap defined will send only one of
+ * these event per user interaction. This variant will send the single tap at all times, with the
+ * optional double tap if the user pressed a second time in a short period of time.
+ *
+ * Warning: Use this only if you know that reporting a single tap followed by a double tap won't be
+ * a problem in your use case.
+ *
+ * @param doubleTapEnabled whether this should listen for double tap events. This value is captured
+ * at the first down movement.
+ * @param onDoubleTap the double tap callback
+ * @param onTap the single tap callback
+ */
+suspend fun PointerInputScope.detectEagerTapGestures(
+ doubleTapEnabled: () -> Boolean,
+ onDoubleTap: (Offset) -> Unit,
+ onTap: () -> Unit,
+) = coroutineScope {
+ awaitEachGesture {
+ val down = awaitFirstDown()
+ down.consume()
+
+ // Capture whether double tap is enabled on first down as this state can change following
+ // the first tap
+ val isDoubleTapEnabled = doubleTapEnabled()
+
+ // wait for first tap up or long press
+ val upOrCancel = waitForUpOrCancellation()
+
+ if (upOrCancel != null) {
+ // tap was successful.
+ upOrCancel.consume()
+ onTap.invoke()
+
+ if (isDoubleTapEnabled) {
+ // check for second tap
+ val secondDown =
+ withTimeoutOrNull(viewConfiguration.doubleTapTimeoutMillis) {
+ val minUptime =
+ upOrCancel.uptimeMillis + viewConfiguration.doubleTapMinTimeMillis
+ var change: PointerInputChange
+ // The second tap doesn't count if it happens before DoubleTapMinTime of the
+ // first tap
+ do {
+ change = awaitFirstDown()
+ } while (change.uptimeMillis < minUptime)
+ change
+ }
+
+ if (secondDown != null) {
+ // Second tap down detected
+
+ // Might have a long second press as the second tap
+ val secondUp = waitForUpOrCancellation()
+ if (secondUp != null) {
+ secondUp.consume()
+ onDoubleTap(secondUp.position)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
index 19eeabd98c88..931639c8b247 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalLockIconViewModel.kt
@@ -130,7 +130,9 @@ constructor(
if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
- keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+ keyguardViewController
+ .get()
+ .showPrimaryBouncer(/* scrim */ true, "CommunalLockIconViewModel#onUserInteraction")
}
deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 7354f4096801..f45bafdfb17e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -16,9 +16,6 @@
package com.android.systemui.dagger;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
-
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -103,7 +100,6 @@ import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.satellite.SatelliteManager;
-import android.view.Choreographer;
import android.view.CrossWindowBlurListeners;
import android.view.IWindowManager;
import android.view.LayoutInflater;
@@ -115,13 +111,9 @@ import android.view.accessibility.CaptioningManager;
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassificationManager;
-import androidx.annotation.NonNull;
import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
import androidx.core.app.NotificationManagerCompat;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.app.viewcapture.ViewCaptureFactory;
import com.android.internal.app.IBatteryStats;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.jank.InteractionJankMonitor;
@@ -135,8 +127,9 @@ import com.android.systemui.dagger.qualifiers.TestHarness;
import com.android.systemui.shared.system.PackageManagerWrapper;
import com.android.systemui.user.utils.UserScopedService;
import com.android.systemui.user.utils.UserScopedServiceImpl;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
+import com.android.systemui.utils.windowmanager.WindowManagerProviderImpl;
-import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -713,38 +706,23 @@ public class FrameworkServicesModule {
@Provides
@Singleton
- static WindowManager provideWindowManager(Context context) {
- return context.getSystemService(WindowManager.class);
- }
-
- /** A window manager working for the default display only. */
- @Provides
- @Singleton
- @Main
- static WindowManager provideMainWindowManager(WindowManager windowManager) {
- return windowManager;
+ static WindowManagerProvider provideWindowManagerProvider() {
+ return new WindowManagerProviderImpl();
}
@Provides
@Singleton
- static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
- WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
- return new ViewCaptureAwareWindowManager(windowManager,
- /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
- /* isViewCaptureEnabled= */ enableViewCaptureTracing());
+ static WindowManager provideWindowManager(Context context,
+ WindowManagerProvider windowManagerProvider) {
+ return windowManagerProvider.getWindowManager(context);
}
+ /** A window manager working for the default display only. */
@Provides
@Singleton
- static ViewCaptureAwareWindowManager.Factory viewCaptureAwareWindowManagerFactory(
- Lazy<ViewCapture> daggerLazyViewCapture) {
- return new ViewCaptureAwareWindowManager.Factory() {
- @NonNull
- @Override
- public ViewCaptureAwareWindowManager create(@NonNull WindowManager windowManager) {
- return provideViewCaptureAwareWindowManager(windowManager, daggerLazyViewCapture);
- }
- };
+ @Main
+ static WindowManager provideMainWindowManager(WindowManager windowManager) {
+ return windowManager;
}
@Provides
@@ -835,12 +813,6 @@ public class FrameworkServicesModule {
@Provides
@Singleton
- static ViewCapture provideViewCapture(Context context) {
- return ViewCaptureFactory.getInstance(context);
- }
-
- @Provides
- @Singleton
@Nullable
static SupervisionManager provideSupervisionManager(Context context) {
return (SupervisionManager) context.getSystemService(Context.SUPERVISION_SERVICE);
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
index 7be323073692..9c3b9b273ab5 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/view/UdfpsAccessibilityOverlay.kt
@@ -20,4 +20,8 @@ import android.content.Context
import android.view.View
/** Overlay to handle under-fingerprint sensor accessibility events. */
-class UdfpsAccessibilityOverlay(context: Context?) : View(context)
+class UdfpsAccessibilityOverlay(context: Context?) : View(context) {
+ init {
+ accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_ASSERTIVE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
index fa849bf5e413..1849bf20abdb 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/ui/viewmodel/UdfpsAccessibilityOverlayViewModel.kt
@@ -56,7 +56,7 @@ abstract class UdfpsAccessibilityOverlayViewModel(
event.getPointerId(0),
event,
overlayParams, /* rotateToPortrait */
- false
+ false,
)
if (
@@ -64,7 +64,7 @@ abstract class UdfpsAccessibilityOverlayViewModel(
event.getPointerId(0),
event,
overlayParams,
- /* rotateTouchToPortrait */ false
+ /* rotateTouchToPortrait */ false,
)
) {
// view only receives motionEvents when [visible] which requires touchExplorationEnabled
@@ -75,10 +75,10 @@ abstract class UdfpsAccessibilityOverlayViewModel(
scaledTouch.x,
scaledTouch.y,
overlayParams,
- /* touchRotatedToPortrait */ false
+ /* touchRotatedToPortrait */ false,
)
if (announceStr != null) {
- v.announceForAccessibility(announceStr)
+ v.contentDescription = announceStr
}
}
// always let the motion events go through to underlying views
diff --git a/packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt b/packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt
new file mode 100644
index 000000000000..6fb3727db1f4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/dagger/PerDisplayCommonModule.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.display.dagger
+
+import android.content.Context
+import android.view.Display
+import com.android.app.displaylib.DisplayRepository
+import com.android.systemui.coroutines.newTracingContext
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.DisplayAware
+import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.DisplayId
+import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent.PerDisplaySingleton
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+
+/** Module providing common dependencies for per-display singletons. */
+@Module
+class PerDisplayCommonModule {
+
+ @Provides
+ @PerDisplaySingleton
+ fun provideDisplay(@DisplayId displayId: Int, displayRepository: DisplayRepository): Display {
+ return displayRepository.getDisplay(displayId)
+ ?: error("Couldn't get the display with id=$displayId")
+ }
+
+ @Provides
+ @PerDisplaySingleton
+ @DisplayAware
+ fun provideDisplayContext(
+ display: Display,
+ @Application context: Context,
+ ): Context {
+ return context.createDisplayContext(display)
+ }
+
+ @Provides
+ @PerDisplaySingleton
+ @DisplayAware
+ fun provideDisplayCoroutineScope(
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @DisplayId displayId: Int,
+ ): CoroutineScope {
+ return CoroutineScope(
+ backgroundDispatcher + newTracingContext("DisplayScope(id=$displayId)")
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt b/packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt
new file mode 100644
index 000000000000..f878aa1ea87b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/dagger/SystemUIDisplaySubcomponent.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.display.dagger
+
+import dagger.BindsInstance
+import dagger.Subcomponent
+import javax.inject.Qualifier
+import javax.inject.Scope
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * Subcomponent for SysUI classes that should be instantiated once per display.
+ *
+ * All display specific classes should be provided with the @DisplayAware annotation. Once the
+ * display is removed, [displayCoroutineScope] gets cancelled. This means that if classes have some
+ * teardown step it should be executed when the scope is cancelled. Also note that the scope is
+ * cancelled in the background, so any teardown logic should be threadsafe. Cancelling on the main
+ * thread is not feasible as it would cause jank.
+ */
+@Subcomponent(modules = [PerDisplayCommonModule::class])
+interface SystemUIDisplaySubcomponent {
+
+ @DisplayAware val displayCoroutineScope: CoroutineScope
+
+ @Subcomponent.Factory
+ interface Factory {
+ fun create(@BindsInstance @DisplayId displayId: Int): SystemUIDisplaySubcomponent
+ }
+
+ /** Scope annotation for singletons associated to a display. */
+ @MustBeDocumented
+ @Retention(AnnotationRetention.RUNTIME)
+ @Scope
+ annotation class PerDisplaySingleton
+
+ /** Qualifier used to represent that the object is provided/bound with the proper display. */
+ @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DisplayAware
+
+ /** Annotates the display id inside the subcomponent. */
+ @Qualifier @Retention(AnnotationRetention.RUNTIME) annotation class DisplayId
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt
new file mode 100644
index 000000000000..a2d3b35740ea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayComponentRepository.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.display.data.repository
+
+import com.android.app.displaylib.PerDisplayInstanceProviderWithTeardown
+import com.android.app.displaylib.PerDisplayInstanceRepositoryImpl
+import com.android.app.displaylib.PerDisplayRepository
+import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.display.dagger.SystemUIDisplaySubcomponent
+import dagger.Module
+import dagger.Provides
+import javax.inject.Inject
+import kotlinx.coroutines.cancel
+
+@SysUISingleton
+class DisplayComponentInstanceProvider
+@Inject
+constructor(private val componentFactory: SystemUIDisplaySubcomponent.Factory) :
+ PerDisplayInstanceProviderWithTeardown<SystemUIDisplaySubcomponent> {
+ override fun createInstance(displayId: Int): SystemUIDisplaySubcomponent? =
+ runCatching { componentFactory.create(displayId) }.getOrNull()
+
+ override fun destroyInstance(instance: SystemUIDisplaySubcomponent) {
+ traceSection("Destroying a display component instance") {
+ instance.displayCoroutineScope.cancel("Cancelling scope associated to the display.")
+ }
+ }
+}
+
+@Module
+object DisplayComponentRepository {
+ @SysUISingleton
+ @Provides
+ fun provideDisplayComponentRepository(
+ repositoryFactory: PerDisplayInstanceRepositoryImpl.Factory<SystemUIDisplaySubcomponent>,
+ instanceProvider: DisplayComponentInstanceProvider,
+ ): PerDisplayRepository<SystemUIDisplaySubcomponent> {
+ return repositoryFactory.create(
+ debugName = "DisplayComponentInstanceProvider",
+ instanceProvider,
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
index 3390640fa6c6..aaaaacef001a 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayWindowPropertiesRepository.kt
@@ -31,6 +31,7 @@ import com.android.systemui.display.shared.model.DisplayWindowProperties
import com.android.systemui.res.R
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.utils.windowmanager.WindowManagerUtils
import com.google.common.collect.HashBasedTable
import com.google.common.collect.Table
import java.io.PrintWriter
@@ -110,7 +111,7 @@ constructor(
return null
}
@SuppressLint("NonInjectedService") // Need to manually get the service
- val windowManager = context.getSystemService(WindowManager::class.java) as WindowManager
+ val windowManager = WindowManagerUtils.getWindowManager(context)
val layoutInflater = LayoutInflater.from(context)
DisplayWindowProperties(displayId, windowType, context, windowManager, layoutInflater)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index a2bcb98e36fe..fd716eea799a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -46,7 +46,6 @@ import androidx.lifecycle.LifecycleService;
import androidx.lifecycle.ServiceLifecycleDispatcher;
import androidx.lifecycle.ViewModelStore;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.compose.animation.scene.OverlayKey;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -117,7 +116,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
@Nullable
private final ComponentName mHomeControlPanelDreamComponent;
private final UiEventLogger mUiEventLogger;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final String mWindowTitle;
// A reference to the {@link Window} used to hold the dream overlay.
@@ -378,7 +377,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
Context context,
DreamOverlayLifecycleOwner lifecycleOwner,
@Main DelayableExecutor executor,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ WindowManager windowManager,
ComplicationComponent.Factory complicationComponentFactory,
DreamComplicationComponent.Factory dreamComplicationComponentFactory,
DreamOverlayComponent.Factory dreamOverlayComponentFactory,
@@ -403,7 +402,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
super(executor);
mContext = context;
mExecutor = executor;
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mScrimManager = scrimManager;
mLowLightDreamComponent = lowLightDreamComponent;
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
index c2974a8d5429..c08a8e297174 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt
@@ -20,7 +20,6 @@ import android.content.Context
import android.graphics.Paint
import android.graphics.PixelFormat
import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView
@@ -38,7 +37,7 @@ constructor(
context: Context,
@Application private val applicationScope: CoroutineScope,
private val viewModel: KeyboardDockingIndicationViewModel,
- private val windowManager: ViewCaptureAwareWindowManager,
+ private val windowManager: WindowManager
) {
private val windowLayoutParams =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 6db2ebc0df2c..099a7f067482 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -140,6 +140,7 @@ import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -364,6 +365,7 @@ public class KeyguardViewMediator implements CoreStartable,
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
private final Lazy<CommunalSceneInteractor> mCommunalSceneInteractor;
+ private final Lazy<CommunalSettingsInteractor> mCommunalSettingsInteractor;
/*
* Records the user id on request to go away, for validation when WM calls back to start the
* exit animation.
@@ -1567,6 +1569,7 @@ public class KeyguardViewMediator implements CoreStartable,
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager wmOcclusionManager) {
mContext = context;
mUserTracker = userTracker;
@@ -1609,6 +1612,7 @@ public class KeyguardViewMediator implements CoreStartable,
mKeyguardInteractor = keyguardInteractor;
mTransitionBootInteractor = transitionBootInteractor;
mCommunalSceneInteractor = communalSceneInteractor;
+ mCommunalSettingsInteractor = communalSettingsInteractor;
mStatusBarStateController = statusBarStateController;
statusBarStateController.addCallback(this);
@@ -2429,9 +2433,18 @@ public class KeyguardViewMediator implements CoreStartable,
private void doKeyguardLocked(Bundle options) {
// If the power button behavior requests to open the glanceable hub.
if (options != null && options.getBoolean(EXTRA_TRIGGER_HUB)) {
- // Set the hub to show immediately when the SysUI window shows, then continue to lock
- // the device.
- mCommunalSceneInteractor.get().showHubFromPowerButton();
+ if (mCommunalSettingsInteractor.get().getAutoOpenEnabled().getValue()) {
+ // Set the hub to show immediately when the SysUI window shows, then continue to
+ // lock the device.
+ mCommunalSceneInteractor.get().showHubFromPowerButton();
+ } else {
+ // If the hub is not available, go to sleep instead of locking. This can happen
+ // because the power button behavior does not check all possible reasons the hub
+ // might be disabled.
+ mPM.goToSleep(android.os.SystemClock.uptimeMillis(),
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ return;
+ }
}
int currentUserId = mSelectedUserInteractor.getSelectedUserId();
@@ -3765,13 +3778,7 @@ public class KeyguardViewMediator implements CoreStartable,
Log.d(TAG, "Status bar manager is disabled for visible background users");
}
} else {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId());
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to force clear flags", e);
- }
+ statusBarServiceDisableForUser(flags, "Failed to force clear flags");
}
}
@@ -3807,18 +3814,29 @@ public class KeyguardViewMediator implements CoreStartable,
// Handled in StatusBarDisableFlagsInteractor.
if (!KeyguardWmStateRefactor.isEnabled()) {
- try {
- mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
- mContext.getPackageName(),
- mSelectedUserInteractor.getSelectedUserId());
- } catch (RemoteException e) {
- Log.d(TAG, "Failed to set disable flags: " + flags, e);
- }
+ statusBarServiceDisableForUser(flags, "Failed to set disable flags: ");
}
}
}
}
+ private void statusBarServiceDisableForUser(int flags, String loggingContext) {
+ Runnable runnable = () -> {
+ try {
+ mStatusBarService.disableForUser(flags, mStatusBarDisableToken,
+ mContext.getPackageName(),
+ mSelectedUserInteractor.getSelectedUserId());
+ } catch (RemoteException e) {
+ Log.d(TAG, loggingContext + " " + flags, e);
+ }
+ };
+ if (com.android.systemui.Flags.bouncerUiRevamp()) {
+ mUiBgExecutor.execute(runnable);
+ } else {
+ runnable.run();
+ }
+ }
+
/**
* Handle message sent by {@link #resetStateLocked}
* @see #RESET
@@ -4099,12 +4117,23 @@ public class KeyguardViewMediator implements CoreStartable,
|| aodShowing != mAodShowing || forceCallbacks;
mShowing = showing;
mAodShowing = aodShowing;
- if (notifyDefaultDisplayCallbacks) {
- notifyDefaultDisplayCallbacks(showing);
- }
- if (updateActivityLockScreenState) {
- updateActivityLockScreenState(showing, aodShowing, reason);
+
+ if (KeyguardWmReorderAtmsCalls.isEnabled()) {
+ if (updateActivityLockScreenState) {
+ updateActivityLockScreenState(showing, aodShowing, reason);
+ }
+ if (notifyDefaultDisplayCallbacks) {
+ notifyDefaultDisplayCallbacks(showing);
+ }
+ } else {
+ if (notifyDefaultDisplayCallbacks) {
+ notifyDefaultDisplayCallbacks(showing);
+ }
+ if (updateActivityLockScreenState) {
+ updateActivityLockScreenState(showing, aodShowing, reason);
+ }
}
+
}
private void notifyDefaultDisplayCallbacks(boolean showing) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmReorderAtmsCalls.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmReorderAtmsCalls.kt
new file mode 100644
index 000000000000..7ac52813ff71
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmReorderAtmsCalls.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the keyguard wm state refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object KeyguardWmReorderAtmsCalls {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_KEYGUARD_WM_REORDER_ATMS_CALLS
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.keyguardWmReorderAtmsCalls()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
index ddccc5d9e96d..41d14b9e727f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardWmStateRefactor.kt
@@ -20,7 +20,16 @@ import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-/** Helper for reading or using the keyguard wm state refactor flag state. */
+/**
+ * Helper for reading or using the keyguard_wm_state_refactor flag state.
+ *
+ * keyguard_wm_state_refactor works both with and without flexiglass (scene_container), but
+ * flexiglass requires keyguard_wm_state_refactor. For this reason, this class will return isEnabled
+ * if either keyguard_wm_state_refactor OR scene_container are enabled. This enables us to roll out
+ * keyguard_wm_state_refactor independently of scene_container, while also ensuring that
+ * scene_container rolling out ahead of keyguard_wm_state_refactor causes code gated by
+ * KeyguardWmStateRefactor to be enabled as well.
+ */
@Suppress("NOTHING_TO_INLINE")
object KeyguardWmStateRefactor {
/** The aconfig flag name */
@@ -30,10 +39,9 @@ object KeyguardWmStateRefactor {
val token: FlagToken
get() = FlagToken(FLAG_NAME, isEnabled)
- /** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.keyguardWmStateRefactor()
+ get() = Flags.keyguardWmStateRefactor() || Flags.sceneContainer()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 6b1248b6983e..1fe6eb9ce7c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
+import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor;
import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
@@ -182,6 +183,7 @@ public interface KeyguardModule {
KeyguardInteractor keyguardInteractor,
KeyguardTransitionBootInteractor transitionBootInteractor,
Lazy<CommunalSceneInteractor> communalSceneInteractor,
+ Lazy<CommunalSettingsInteractor> communalSettingsInteractor,
WindowManagerOcclusionManager windowManagerOcclusionManager) {
return new KeyguardViewMediator(
context,
@@ -234,6 +236,7 @@ public interface KeyguardModule {
keyguardInteractor,
transitionBootInteractor,
communalSceneInteractor,
+ communalSettingsInteractor,
windowManagerOcclusionManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index a3796ab5ee27..cc03e49b462b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -145,7 +145,7 @@ constructor(
KeyguardState.GLANCEABLE_HUB
} else if (isOccluded && !isDreaming) {
KeyguardState.OCCLUDED
- } else if (hubV2 && isDreaming) {
+ } else if (isDreaming) {
KeyguardState.DREAMING
} else if (hubV2 && isIdleOnCommunal) {
if (SceneContainerFlag.isEnabled) return@collect
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index f8c7a86687dd..f4e804ac5abf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -24,6 +24,7 @@ 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.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.KeyguardWmStateRefactor
@@ -62,6 +63,7 @@ constructor(
override val internalTransitionInteractor: InternalKeyguardTransitionInteractor,
transitionInteractor: KeyguardTransitionInteractor,
@Background private val scope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
@@ -175,7 +177,7 @@ constructor(
private fun listenForLockscreenToPrimaryBouncerDragging() {
if (SceneContainerFlag.isEnabled) return
var transitionId: UUID? = null
- scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
+ applicationScope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") {
shadeRepository.legacyShadeExpansion.collect { shadeExpansion ->
val statusBarState = keyguardInteractor.statusBarState.value
val isKeyguardUnlocked = keyguardInteractor.isKeyguardDismissible.value
@@ -204,7 +206,7 @@ constructor(
id,
// This maps the shadeExpansion to a much faster curve, to match
// the existing logic
- 1f - MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, shadeExpansion),
+ 1f - MathUtils.constrainedMap(0f, 1f, 0.88f, 1f, shadeExpansion),
nextState,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 0a4022ad4de8..e6f8406726f0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -159,7 +159,10 @@ constructor(
if (alternateBouncerInteractor.canShowAlternateBouncer.value) {
alternateBouncerInteractor.forceShow()
} else {
- primaryBouncerInteractor.show(true)
+ primaryBouncerInteractor.show(
+ true,
+ "KeyguardDismissInteractor#dismissKeyguardWithCallback",
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 7977000ed5c8..2d5ff61a5015 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,7 +19,6 @@ import android.app.StatusBarManager
import android.graphics.Point
import android.util.Log
import android.util.MathUtils
-import com.android.app.animation.Interpolators
import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
import com.android.systemui.common.shared.model.NotificationContainerBounds
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
@@ -371,9 +370,11 @@ constructor(
currentKeyguardState == LOCKSCREEN &&
legacyShadeExpansion != 1f
) {
- emit(MathUtils.constrainedMap(0f, 1f, 0.95f, 1f, legacyShadeExpansion))
+ emit(MathUtils.constrainedMap(0f, 1f, 0.82f, 1f, legacyShadeExpansion))
} else if (
- (legacyShadeExpansion == 0f || legacyShadeExpansion == 1f) && !onGlanceableHub
+ !onGlanceableHub &&
+ isKeyguardDismissible &&
+ (legacyShadeExpansion == 0f || legacyShadeExpansion == 1f)
) {
// Resets alpha state
emit(1f)
@@ -401,15 +402,7 @@ constructor(
// 0f and 1f need to be ignored in the legacy shade expansion. These can
// flip arbitrarily as the legacy shade is reset, and would cause the
// translation value to jump around unexpectedly.
- emit(
- MathUtils.lerp(
- translationDistance,
- 0,
- Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(
- legacyShadeExpansion
- ),
- )
- )
+ emit(MathUtils.lerp(translationDistance, 0, legacyShadeExpansion))
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
index 6d9b276031e9..ced96e93d87d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardKeyEventInteractor.kt
@@ -136,7 +136,10 @@ constructor(
return true
}
StatusBarState.KEYGUARD -> {
- statusBarKeyguardViewManager.showPrimaryBouncer(true)
+ statusBarKeyguardViewManager.showPrimaryBouncer(
+ true,
+ "KeyguardKeyEventInteractor#collapseShadeLockedOrShowPrimaryBouncer",
+ )
return true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index 68d595ebf0b6..b4e9d8296a74 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -196,39 +196,50 @@ constructor(
.distinctUntilChanged()
}
- private val lockscreenVisibilityWithScenes =
- combine(
- sceneInteractor.get().transitionState.flatMapLatestConflated {
- when (it) {
- is Idle -> {
- when (it.currentScene) {
- in keyguardContent -> flowOf(true)
- in nonKeyguardContent -> flowOf(false)
- in keyguardAgnosticContent -> isDeviceNotEnteredDirectly
- else ->
- throw IllegalStateException("Unknown scene: ${it.currentScene}")
- }
- }
- is Transition -> {
- when {
- it.isTransitioningSets(from = keyguardContent) -> flowOf(true)
- it.isTransitioningSets(from = nonKeyguardContent) -> flowOf(false)
- it.isTransitioningSets(from = keyguardAgnosticContent) ->
- isDeviceNotEnteredDirectly
- else ->
- throw IllegalStateException(
- "Unknown content: ${it.fromContent}"
- )
+ private val lockscreenVisibilityWithScenes: Flow<Boolean> =
+ // The scene container visibility into account as that will be forced to false when the
+ // device isn't yet provisioned (e.g. still in the setup wizard).
+ sceneInteractor.get().isVisible.flatMapLatestConflated { isVisible ->
+ if (isVisible) {
+ combine(
+ sceneInteractor.get().transitionState.flatMapLatestConflated {
+ when (it) {
+ is Idle ->
+ when (it.currentScene) {
+ in keyguardContent -> flowOf(true)
+ in nonKeyguardContent -> flowOf(false)
+ in keyguardAgnosticContent -> isDeviceNotEnteredDirectly
+ else ->
+ throw IllegalStateException(
+ "Unknown scene: ${it.currentScene}"
+ )
+ }
+ is Transition ->
+ when {
+ it.isTransitioningSets(from = keyguardContent) ->
+ flowOf(true)
+ it.isTransitioningSets(from = nonKeyguardContent) ->
+ flowOf(false)
+ it.isTransitioningSets(from = keyguardAgnosticContent) ->
+ isDeviceNotEnteredDirectly
+ else ->
+ throw IllegalStateException(
+ "Unknown content: ${it.fromContent}"
+ )
+ }
}
- }
+ },
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair,
+ )
+ .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
+ lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
}
- },
- wakeToGoneInteractor.canWakeDirectlyToGone,
- ::Pair,
- )
- .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
- lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
+ } else {
+ // Lockscreen is never visible when the scene container is invisible.
+ flowOf(false)
}
+ }
private val lockscreenVisibilityLegacy =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 70a827d5e45b..1ea47ec670af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.kotlin.DisposableHandles
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
@@ -56,6 +57,7 @@ object DeviceEntryIconViewBinder {
@JvmStatic
fun bind(
applicationScope: CoroutineScope,
+ mainImmediateDispatcher: CoroutineDispatcher,
view: DeviceEntryIconView,
viewModel: DeviceEntryIconViewModel,
fgViewModel: DeviceEntryForegroundViewModel,
@@ -96,6 +98,32 @@ object DeviceEntryIconViewBinder {
}
disposables +=
+ view.repeatWhenAttached(mainImmediateDispatcher) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch("$TAG#viewModel.useBackgroundProtection") {
+ viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
+ if (useBackgroundProtection) {
+ bgView.visibility = View.VISIBLE
+ } else {
+ bgView.visibility = View.GONE
+ }
+ }
+ }
+ launch("$TAG#viewModel.burnInOffsets") {
+ viewModel.burnInOffsets.collect { burnInOffsets ->
+ view.translationX = burnInOffsets.x.toFloat()
+ view.translationY = burnInOffsets.y.toFloat()
+ view.aodFpDrawable.progress = burnInOffsets.progress
+ }
+ }
+
+ launch("$TAG#viewModel.deviceEntryViewAlpha") {
+ viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
+ }
+ }
+ }
+
+ disposables +=
view.repeatWhenAttached {
// Repeat on CREATED so that the view will always observe the entire
// GONE => AOD transition (even though the view may not be visible until the middle
@@ -152,26 +180,6 @@ object DeviceEntryIconViewBinder {
}
}
}
- launch("$TAG#viewModel.useBackgroundProtection") {
- viewModel.useBackgroundProtection.collect { useBackgroundProtection ->
- if (useBackgroundProtection) {
- bgView.visibility = View.VISIBLE
- } else {
- bgView.visibility = View.GONE
- }
- }
- }
- launch("$TAG#viewModel.burnInOffsets") {
- viewModel.burnInOffsets.collect { burnInOffsets ->
- view.translationX = burnInOffsets.x.toFloat()
- view.translationY = burnInOffsets.y.toFloat()
- view.aodFpDrawable.progress = burnInOffsets.progress
- }
- }
-
- launch("$TAG#viewModel.deviceEntryViewAlpha") {
- viewModel.deviceEntryViewAlpha.collect { alpha -> view.alpha = alpha }
- }
}
}
@@ -212,7 +220,7 @@ object DeviceEntryIconViewBinder {
}
disposables +=
- bgView.repeatWhenAttached {
+ bgView.repeatWhenAttached(mainImmediateDispatcher) {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#bgViewModel.alpha") {
bgViewModel.alpha.collect { alpha -> bgView.alpha = alpha }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index aeb327035c79..60460bf68c12 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -135,7 +135,10 @@ object KeyguardRootViewBinder {
} else if (
event.action == MotionEvent.ACTION_UP && !event.isTouchscreenSource()
) {
- statusBarKeyguardViewManager?.showBouncer(true)
+ statusBarKeyguardViewManager?.showBouncer(
+ true,
+ "KeyguardRootViewBinder: click on lockscreen",
+ )
consumed = true
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
index 1fd609dba637..853f1769994e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/SideFpsProgressBar.kt
@@ -26,7 +26,6 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
import android.widget.ProgressBar
import androidx.core.view.isGone
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import javax.inject.Inject
@@ -38,7 +37,7 @@ class SideFpsProgressBar
@Inject
constructor(
private val layoutInflater: LayoutInflater,
- private val windowManager: ViewCaptureAwareWindowManager,
+ private val windowManager: WindowManager,
) {
private var overlayView: View? = null
@@ -91,7 +90,7 @@ constructor(
) {
if (overlayView == null) {
overlayView = layoutInflater.inflate(R.layout.sidefps_progress_bar, null, false)
- windowManager.addView(requireNotNull(overlayView), overlayViewParams)
+ windowManager.addView(overlayView, overlayViewParams)
progressBar?.pivotX = 0.0f
progressBar?.pivotY = 0.0f
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 58d482b8a66f..9c8f04b419fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -28,6 +28,7 @@ import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.biometrics.AuthController
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.shared.model.KeyguardSection
@@ -48,6 +49,7 @@ import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.VibratorHelper
import dagger.Lazy
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
@@ -56,6 +58,7 @@ class DefaultDeviceEntrySection
@Inject
constructor(
@Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
private val authController: AuthController,
private val windowManager: WindowManager,
@ShadeDisplayAware private val context: Context,
@@ -91,6 +94,7 @@ constructor(
disposableHandle =
DeviceEntryIconViewBinder.bind(
applicationScope,
+ mainDispatcher,
it,
deviceEntryIconViewModel.get(),
deviceEntryForegroundViewModel.get(),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
index 9038922466df..803e2c0b0f96 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerUdfpsIconViewModel.kt
@@ -106,7 +106,10 @@ constructor(
}
fun onTapped() {
- statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+ statusBarKeyguardViewManager.showPrimaryBouncer(
+ /* scrimmed */ true,
+ "AlternateBouncerUdfpsIconViewModel#onTapped",
+ )
}
val bgColor: Flow<Int> = deviceEntryBackgroundViewModel.color
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
index cff651114c93..45f43bb484c8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModel.kt
@@ -47,7 +47,9 @@ constructor(
/** Reports the alternate bouncer visible state if the scene container flag is enabled. */
val isVisible: Flow<Boolean> =
- alternateBouncerInteractor.get().isVisible.onEach { SceneContainerFlag.unsafeAssertInNewMode() }
+ alternateBouncerInteractor.get().isVisible.onEach {
+ SceneContainerFlag.unsafeAssertInNewMode()
+ }
/** Progress to a fully transitioned alternate bouncer. 1f represents fully transitioned. */
val transitionToAlternateBouncerProgress: Flow<Float> =
@@ -63,7 +65,10 @@ constructor(
transitionToAlternateBouncerProgress.map { it == 1f }.distinctUntilChanged()
fun onTapped() {
- statusBarKeyguardViewManager.showPrimaryBouncer(/* scrimmed */ true)
+ statusBarKeyguardViewManager.showPrimaryBouncer(
+ /* scrimmed */ true,
+ "AlternateBouncerViewModel#onTapped",
+ )
}
fun onRemovedFromWindow() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 13cd5839e1c8..9b4bd67f227e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -257,7 +257,9 @@ constructor(
if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
} else {
- keyguardViewController.get().showPrimaryBouncer(/* scrim */ true)
+ keyguardViewController
+ .get()
+ .showPrimaryBouncer(/* scrim */ true, "DeviceEntryIconViewModel#onUserInteraction")
}
deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
index 9312bca04994..a0458f0172f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModel.kt
@@ -64,7 +64,7 @@ constructor(
val shortcutsAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
- duration = FromLockscreenTransitionInteractor.TO_PRIMARY_BOUNCER_DURATION,
+ duration = 200.milliseconds,
onStep = alphaForAnimationStep,
// Rapid swipes to bouncer, and may end up skipping intermediate values that would've
// caused a complete fade out of lockscreen elements. Ensure it goes to 0f.
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
index 2cd5016cb206..d053dd2b54d1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImpl.kt
@@ -248,7 +248,7 @@ constructor(
// Update loading state with actual active value
mediaFilterRepository.selectedUserEntries.value[lastActiveId]?.let {
mediaFilterRepository.addMediaDataLoadingState(
- MediaDataLoadingModel.Loaded(lastActiveId, immediately)
+ MediaDataLoadingModel.Loaded(lastActiveId)
)
mediaLogger.logMediaLoaded(lastActiveId, it.active, "expiring reactivated id")
listeners.forEach { listener ->
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
index c8a02faea58a..323e5cb76aba 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/model/MediaDataLoadingModel.kt
@@ -26,13 +26,10 @@ sealed class MediaDataLoadingModel {
/** Media data has been loaded. */
data class Loaded(
override val instanceId: InstanceId,
- val immediatelyUpdateUi: Boolean = true,
val receivedSmartspaceCardLatency: Int = 0,
val isSsReactivated: Boolean = false,
) : MediaDataLoadingModel()
/** Media data has been removed. */
- data class Removed(
- override val instanceId: InstanceId,
- ) : MediaDataLoadingModel()
+ data class Removed(override val instanceId: InstanceId) : MediaDataLoadingModel()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 71b3223b77be..800220ee962a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -68,7 +68,7 @@ import com.android.systemui.media.controls.ui.view.MediaHostState
import com.android.systemui.media.controls.ui.view.MediaScrollView
import com.android.systemui.media.controls.ui.view.MediaViewHolder
import com.android.systemui.media.controls.ui.viewmodel.MediaCarouselViewModel
-import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import com.android.systemui.media.controls.util.MediaUiEventLogger
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
@@ -324,8 +324,8 @@ constructor(
private var widthInSceneContainerPx = 0
private var heightInSceneContainerPx = 0
- private val controllerById = mutableMapOf<String, MediaViewController>()
- private val commonViewModels = mutableListOf<MediaCommonViewModel>()
+ private val controllerById = mutableMapOf<InstanceId, MediaViewController>()
+ private val controlViewModels = mutableListOf<MediaControlViewModel>()
private val isOnGone =
keyguardTransitionInteractor
@@ -566,10 +566,10 @@ constructor(
private fun listenForMediaItemsChanges(scope: CoroutineScope): Job {
return scope.launch {
mediaCarouselViewModel.mediaItems.collectLatest {
- val diffUtilCallback = MediaViewModelCallback(commonViewModels, it)
+ val diffUtilCallback = MediaViewModelCallback(controlViewModels, it)
val listUpdateCallback =
MediaViewModelListUpdateCallback(
- old = commonViewModels,
+ old = controlViewModels,
new = it,
onAdded = this@MediaCarouselController::onAdded,
onUpdated = this@MediaCarouselController::onUpdated,
@@ -590,7 +590,7 @@ constructor(
}
private fun onAdded(
- commonViewModel: MediaCommonViewModel,
+ controlViewModel: MediaControlViewModel,
position: Int,
configChanged: Boolean = false,
) {
@@ -601,64 +601,52 @@ constructor(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
- when (commonViewModel) {
- is MediaCommonViewModel.MediaControl -> {
- val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
- viewController.widthInSceneContainerPx = widthInSceneContainerPx
- viewController.heightInSceneContainerPx = heightInSceneContainerPx
- viewController.attachPlayer(viewHolder)
- viewController.mediaViewHolder?.player?.layoutParams = lp
- if (configChanged) {
- commonViewModel.controlViewModel.onMediaConfigChanged()
- }
- MediaControlViewBinder.bind(
- viewHolder,
- commonViewModel.controlViewModel,
- viewController,
- falsingManager,
- backgroundDispatcher,
- mainDispatcher,
- )
- mediaContent.addView(viewHolder.player, position)
- controllerById[commonViewModel.instanceId.toString()] = viewController
- }
+ val viewHolder = MediaViewHolder.create(LayoutInflater.from(context), mediaContent)
+ viewController.widthInSceneContainerPx = widthInSceneContainerPx
+ viewController.heightInSceneContainerPx = heightInSceneContainerPx
+ viewController.attachPlayer(viewHolder)
+ viewController.mediaViewHolder?.player?.layoutParams = lp
+ if (configChanged) {
+ controlViewModel.onMediaConfigChanged()
}
+ MediaControlViewBinder.bind(
+ viewHolder,
+ controlViewModel,
+ viewController,
+ falsingManager,
+ backgroundDispatcher,
+ mainDispatcher,
+ )
+ mediaContent.addView(viewHolder.player, position)
+ controllerById[controlViewModel.instanceId] = viewController
viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
updateViewControllerToState(viewController, noAnimation = true)
updatePageIndicator()
- if (
- commonViewModel is MediaCommonViewModel.MediaControl && commonViewModel.isMediaFromRec
- ) {
- mediaCarouselScrollHandler.scrollToPlayer(
- mediaCarouselScrollHandler.visibleMediaIndex,
- destIndex = 0,
- )
- }
mediaCarouselScrollHandler.onPlayersChanged()
mediaFrame.requiresRemeasuring = true
- commonViewModel.onAdded(commonViewModel)
+ controlViewModel.onAdded(controlViewModel)
}
- private fun onUpdated(commonViewModel: MediaCommonViewModel, position: Int) {
- commonViewModel.onUpdated(commonViewModel)
+ private fun onUpdated(controlViewModel: MediaControlViewModel, position: Int) {
+ controlViewModel.onUpdated(controlViewModel)
updatePageIndicator()
mediaCarouselScrollHandler.onPlayersChanged()
}
- private fun onRemoved(commonViewModel: MediaCommonViewModel) {
- val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
+ private fun onRemoved(controlViewModel: MediaControlViewModel) {
+ val id = controlViewModel.instanceId
controllerById.remove(id)?.let {
mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
mediaContent.removeView(it.mediaViewHolder!!.player)
it.onDestroy()
mediaCarouselScrollHandler.onPlayersChanged()
updatePageIndicator()
- commonViewModel.onRemoved(true)
+ controlViewModel.onRemoved(true)
}
}
- private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) {
- val id = (commonViewModel as MediaCommonViewModel.MediaControl).instanceId.toString()
+ private fun onMoved(controlViewModel: MediaControlViewModel, from: Int, to: Int) {
+ val id = controlViewModel.instanceId
controllerById[id]?.let {
mediaContent.removeViewAt(from)
mediaContent.addView(it.mediaViewHolder!!.player, to)
@@ -667,19 +655,12 @@ constructor(
mediaCarouselScrollHandler.onPlayersChanged()
}
- private fun setNewViewModelsList(viewModels: List<MediaCommonViewModel>) {
- commonViewModels.clear()
- commonViewModels.addAll(viewModels)
+ private fun setNewViewModelsList(viewModels: List<MediaControlViewModel>) {
+ controlViewModels.clear()
+ controlViewModels.addAll(viewModels)
// Ensure we only show the needed UMOs in media carousel.
- val viewIds =
- viewModels
- .map { mediaCommonViewModel ->
- (mediaCommonViewModel as MediaCommonViewModel.MediaControl)
- .instanceId
- .toString()
- }
- .toHashSet()
+ val viewIds = viewModels.map { controlViewModel -> controlViewModel.instanceId }.toHashSet()
controllerById
.filter { !viewIds.contains(it.key) }
.forEach {
@@ -976,9 +957,8 @@ constructor(
ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
if (recreateMedia) {
mediaContent.removeAllViews()
- commonViewModels.forEachIndexed { index, viewModel ->
- val mediaControlViewModel = (viewModel as MediaCommonViewModel.MediaControl)
- controllerById[mediaControlViewModel.instanceId.toString()]?.onDestroy()
+ controlViewModels.forEachIndexed { index, viewModel ->
+ controllerById[viewModel.instanceId]?.onDestroy()
onAdded(viewModel, index, configChanged = true)
}
}
@@ -1309,7 +1289,7 @@ constructor(
println("dataKeys: ${MediaPlayerData.dataKeys()}")
println("orderedPlayerSortKeys: ${MediaPlayerData.playerKeys()}")
println("visiblePlayerSortKeys: ${MediaPlayerData.visiblePlayerKeys()}")
- println("commonViewModels: $commonViewModels")
+ println("controlViewModels: $controlViewModels")
println("smartspaceMediaData: ${MediaPlayerData.smartspaceMediaData}")
println("shouldPrioritizeSs: ${MediaPlayerData.shouldPrioritizeSs}")
println("current size: $currentCarouselWidth x $currentCarouselHeight")
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
index 2fc44ad3cce6..ad2d46263faf 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelCallback.kt
@@ -17,12 +17,12 @@
package com.android.systemui.media.controls.ui.util
import androidx.recyclerview.widget.DiffUtil
-import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
/** A [DiffUtil.Callback] to calculate difference between old and new media view-model list. */
class MediaViewModelCallback(
- private val old: List<MediaCommonViewModel>,
- private val new: List<MediaCommonViewModel>,
+ private val old: List<MediaControlViewModel>,
+ private val new: List<MediaControlViewModel>,
) : DiffUtil.Callback() {
override fun getOldListSize(): Int {
@@ -36,27 +36,12 @@ class MediaViewModelCallback(
override fun areItemsTheSame(oldIndex: Int, newIndex: Int): Boolean {
val oldItem = old[oldIndex]
val newItem = new[newIndex]
- return if (
- oldItem is MediaCommonViewModel.MediaControl &&
- newItem is MediaCommonViewModel.MediaControl
- ) {
- oldItem.instanceId == newItem.instanceId
- } else {
- false
- }
+ return oldItem.instanceId == newItem.instanceId
}
override fun areContentsTheSame(oldIndex: Int, newIndex: Int): Boolean {
val oldItem = old[oldIndex]
val newItem = new[newIndex]
- return if (
- oldItem is MediaCommonViewModel.MediaControl &&
- newItem is MediaCommonViewModel.MediaControl
- ) {
- oldItem.immediatelyUpdateUi == newItem.immediatelyUpdateUi &&
- oldItem.updateTime == newItem.updateTime
- } else {
- false
- }
+ return oldItem.updateTime == newItem.updateTime
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
index 6022b7b1fc13..1db8c58d4140 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/util/MediaViewModelListUpdateCallback.kt
@@ -17,17 +17,17 @@
package com.android.systemui.media.controls.ui.util
import androidx.recyclerview.widget.ListUpdateCallback
-import com.android.systemui.media.controls.ui.viewmodel.MediaCommonViewModel
+import com.android.systemui.media.controls.ui.viewmodel.MediaControlViewModel
import kotlin.math.min
/** A [ListUpdateCallback] to apply media events needed to reach the new state. */
class MediaViewModelListUpdateCallback(
- private val old: List<MediaCommonViewModel>,
- private val new: List<MediaCommonViewModel>,
- private val onAdded: (MediaCommonViewModel, Int) -> Unit,
- private val onUpdated: (MediaCommonViewModel, Int) -> Unit,
- private val onRemoved: (MediaCommonViewModel) -> Unit,
- private val onMoved: (MediaCommonViewModel, Int, Int) -> Unit,
+ private val old: List<MediaControlViewModel>,
+ private val new: List<MediaControlViewModel>,
+ private val onAdded: (MediaControlViewModel, Int) -> Unit,
+ private val onUpdated: (MediaControlViewModel, Int) -> Unit,
+ private val onRemoved: (MediaControlViewModel) -> Unit,
+ private val onMoved: (MediaControlViewModel, Int, Int) -> Unit,
) : ListUpdateCallback {
override fun onInserted(position: Int, count: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
index dfaee4434bcf..54d3151694bd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCarouselViewModel.kt
@@ -56,7 +56,7 @@ constructor(
val hasAnyMediaOrRecommendations: StateFlow<Boolean> = interactor.hasAnyMediaOrRecommendation
val hasActiveMediaOrRecommendations: StateFlow<Boolean> =
interactor.hasActiveMediaOrRecommendation
- val mediaItems: StateFlow<List<MediaCommonViewModel>> =
+ val mediaItems: StateFlow<List<MediaControlViewModel>> =
interactor.currentMedia
.map { sortedItems ->
val mediaList = buildList {
@@ -91,8 +91,7 @@ constructor(
var updateHostVisibility: () -> Unit = {}
- private val mediaControlByInstanceId =
- mutableMapOf<InstanceId, MediaCommonViewModel.MediaControl>()
+ private val mediaControlByInstanceId = mutableMapOf<InstanceId, MediaControlViewModel>()
private var modelsPendingRemoval: MutableSet<MediaCommonModel> = mutableSetOf()
@@ -108,18 +107,16 @@ constructor(
interactor.reorderMedia()
}
- private fun toViewModel(
- commonModel: MediaCommonModel.MediaControl
- ): MediaCommonViewModel.MediaControl {
+ private fun toViewModel(commonModel: MediaCommonModel.MediaControl): MediaControlViewModel {
val instanceId = commonModel.mediaLoadedModel.instanceId
- return mediaControlByInstanceId[instanceId]?.copy(
- immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi,
- updateTime = commonModel.updateTime,
- )
- ?: MediaCommonViewModel.MediaControl(
+ return mediaControlByInstanceId[instanceId]?.copy(updateTime = commonModel.updateTime)
+ ?: MediaControlViewModel(
+ applicationContext = applicationContext,
+ backgroundDispatcher = backgroundDispatcher,
+ backgroundExecutor = backgroundExecutor,
+ interactor = controlInteractorFactory.create(instanceId),
+ logger = logger,
instanceId = instanceId,
- immediatelyUpdateUi = commonModel.mediaLoadedModel.immediatelyUpdateUi,
- controlViewModel = createMediaControlViewModel(instanceId),
onAdded = {
mediaLogger.logMediaCardAdded(instanceId)
onMediaControlAddedOrUpdated(it, commonModel)
@@ -130,31 +127,20 @@ constructor(
mediaLogger.logMediaCardRemoved(instanceId)
},
onUpdated = { onMediaControlAddedOrUpdated(it, commonModel) },
- isMediaFromRec = commonModel.isMediaFromRec,
updateTime = commonModel.updateTime,
)
.also { mediaControlByInstanceId[instanceId] = it }
}
- private fun createMediaControlViewModel(instanceId: InstanceId): MediaControlViewModel {
- return MediaControlViewModel(
- applicationContext = applicationContext,
- backgroundDispatcher = backgroundDispatcher,
- backgroundExecutor = backgroundExecutor,
- interactor = controlInteractorFactory.create(instanceId),
- logger = logger,
- )
- }
-
private fun onMediaControlAddedOrUpdated(
- commonViewModel: MediaCommonViewModel,
+ controlViewModel: MediaControlViewModel,
commonModel: MediaCommonModel.MediaControl,
) {
if (commonModel.canBeRemoved && !Utils.useMediaResumption(applicationContext)) {
// This media control is due for removal as it is now paused + timed out, and resumption
// setting is off.
if (isReorderingAllowed()) {
- commonViewModel.onRemoved(true)
+ controlViewModel.onRemoved(true)
} else {
modelsPendingRemoval.add(commonModel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
deleted file mode 100644
index d493d57051f7..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaCommonViewModel.kt
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.ui.viewmodel
-
-import com.android.internal.logging.InstanceId
-
-/** Models media view model UI state. */
-sealed class MediaCommonViewModel {
-
- abstract val onAdded: (MediaCommonViewModel) -> Unit
- abstract val onRemoved: (Boolean) -> Unit
- abstract val onUpdated: (MediaCommonViewModel) -> Unit
-
- data class MediaControl(
- val instanceId: InstanceId,
- val immediatelyUpdateUi: Boolean,
- val controlViewModel: MediaControlViewModel,
- override val onAdded: (MediaCommonViewModel) -> Unit,
- override val onRemoved: (Boolean) -> Unit,
- override val onUpdated: (MediaCommonViewModel) -> Unit,
- val isMediaFromRec: Boolean = false,
- val updateTime: Long = 0,
- ) : MediaCommonViewModel()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
index 015274a10330..31cfb8479dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModel.kt
@@ -45,12 +45,17 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
/** Models UI state and handles user input for a media control. */
-class MediaControlViewModel(
+data class MediaControlViewModel(
@Application private val applicationContext: Context,
@Background private val backgroundDispatcher: CoroutineDispatcher,
@Background private val backgroundExecutor: Executor,
private val interactor: MediaControlInteractor,
private val logger: MediaUiEventLogger,
+ val instanceId: InstanceId,
+ val onAdded: (MediaControlViewModel) -> Unit,
+ val onRemoved: (Boolean) -> Unit,
+ val onUpdated: (MediaControlViewModel) -> Unit,
+ val updateTime: Long = 0,
) {
val player: Flow<MediaPlayerViewModel?> =
interactor.mediaControl
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
index ac1672db9375..e3990d25f94e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterBase.java
@@ -237,8 +237,7 @@ public abstract class MediaOutputAdapterBase extends RecyclerView.Adapter<Recycl
clickListener = v -> onItemClick(v, device);
}
} else {
- deviceStatusIcon = getDeviceStatusIcon(device,
- device.hasOngoingSession());
+ deviceStatusIcon = getDeviceStatusIcon(device);
clickListener = getClickListenerBasedOnSelectionBehavior(device);
}
deviceDisabled = clickListener == null;
@@ -302,12 +301,8 @@ public abstract class MediaOutputAdapterBase extends RecyclerView.Adapter<Recycl
}
@Nullable
- private Drawable getDeviceStatusIcon(MediaDevice device, boolean hasOngoingSession) {
- if (hasOngoingSession) {
- return mContext.getDrawable(R.drawable.ic_sound_bars_anim);
- } else {
- return Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(device, mContext);
- }
+ private Drawable getDeviceStatusIcon(MediaDevice device) {
+ return Api34Impl.getDeviceStatusIconBasedOnSelectionBehavior(device, mContext);
}
protected void onExpandGroupButtonClicked() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
index 6ab4a52dc919..7ab6b3cfb8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapterLegacy.java
@@ -231,7 +231,7 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
updateFullItemClickListener(clickListener);
updateContentAlpha(deviceDisabled);
updateSubtitle(subtitle);
- updateDeviceStatusIcon(deviceStatusIcon);
+ updateDeviceStatusIcon(deviceStatusIcon, ongoingSessionStatus, connectionState);
updateItemBackground(connectionState);
}
@@ -523,11 +523,20 @@ public class MediaOutputAdapterLegacy extends MediaOutputAdapterBase {
mStatusIcon.setAlpha(alphaValue);
}
- private void updateDeviceStatusIcon(@Nullable Drawable deviceStatusIcon) {
- if (deviceStatusIcon == null) {
+ private void updateDeviceStatusIcon(@Nullable Drawable deviceStatusIcon,
+ @Nullable OngoingSessionStatus ongoingSessionStatus,
+ ConnectionState connectionState) {
+ boolean showOngoingSession =
+ ongoingSessionStatus != null && connectionState == ConnectionState.DISCONNECTED;
+ if (deviceStatusIcon == null && !showOngoingSession) {
mStatusIcon.setVisibility(View.GONE);
} else {
- mStatusIcon.setImageDrawable(deviceStatusIcon);
+ if (showOngoingSession) {
+ mStatusIcon.setImageDrawable(
+ mContext.getDrawable(R.drawable.ic_sound_bars_anim));
+ } else {
+ mStatusIcon.setImageDrawable(deviceStatusIcon);
+ }
mStatusIcon.setImageTintList(ColorStateList.valueOf(
mController.getColorSchemeLegacy().getColorItemContent()));
if (deviceStatusIcon instanceof AnimatedVectorDrawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 2a23620839e5..6a2c4519e1a5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -35,7 +35,6 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import com.android.app.animation.Interpolators
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.InstanceId
import com.android.internal.widget.CachingIconView
import com.android.systemui.common.shared.model.ContentDescription
@@ -73,7 +72,7 @@ constructor(
private val commandQueue: CommandQueue,
context: Context,
logger: MediaTttReceiverLogger,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
@Main private val mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
@@ -90,7 +89,7 @@ constructor(
TemporaryViewDisplayController<ChipReceiverInfo, MediaTttReceiverLogger>(
context,
logger,
- viewCaptureAwareWindowManager,
+ windowManager,
mainExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
index 9265bfb2f66b..5ffa7fa0ef8d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt
@@ -26,13 +26,13 @@ import android.graphics.Shader
import android.util.AttributeSet
import android.view.View
import android.view.WindowManager
-import androidx.core.content.getSystemService
import androidx.core.content.res.use
import com.android.systemui.res.R
import com.android.systemui.mediaprojection.appselector.data.RecentTask
import com.android.systemui.shared.recents.model.ThumbnailData
import com.android.systemui.shared.recents.utilities.PreviewPositionHelper
import com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen
+import com.android.systemui.utils.windowmanager.WindowManagerUtils
/**
* Custom view that shows a thumbnail preview of one recent task based on [ThumbnailData].
@@ -53,7 +53,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
}
}
- private val windowManager: WindowManager = context.getSystemService()!!
+ private val windowManager: WindowManager = WindowManagerUtils.getWindowManager(context)
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val backgroundPaint =
Paint(Paint.ANTI_ALIAS_FLAG).apply { color = defaultBackgroundColor }
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
index c829471f53f3..57fd1e790f0b 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProvider.kt
@@ -19,6 +19,7 @@ package com.android.systemui.mediaprojection.appselector.view
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
+import android.view.WindowManager
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorScope
@@ -36,6 +37,7 @@ constructor(
private val context: Context,
private val windowMetricsProvider: WindowMetricsProvider,
private val configurationController: ConfigurationController,
+ private val windowManager: WindowManager,
) : CallbackController<TaskPreviewSizeListener>, ConfigurationListener, DefaultLifecycleObserver {
/** Returns the size of the task preview on the screen in pixels */
@@ -65,7 +67,7 @@ constructor(
val width = maxWindowBounds.width()
var height = maximumWindowHeight
- val isLargeScreen = isLargeScreen(context)
+ val isLargeScreen = isLargeScreen(windowManager, context.resources)
if (isLargeScreen) {
val taskbarSize = windowMetricsProvider.currentWindowInsets.bottom
height -= taskbarSize
diff --git a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
index 4559a7aea1a2..7b3f4c61088b 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SceneContainerPlugin.kt
@@ -79,6 +79,7 @@ constructor(
SceneContainerPluginState(
scene = idleState.currentScene,
overlays = idleState.currentOverlays,
+ isVisible = sceneInteractor.get().isVisible.value,
invisibleDueToOcclusion = invisibleDueToOcclusion,
)
)
@@ -100,12 +101,17 @@ constructor(
mapOf<Long, (SceneContainerPluginState) -> Boolean>(
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE to
{
- it.scene != Scenes.Gone || it.overlays.isNotEmpty()
+ when {
+ !it.isVisible -> false
+ it.scene != Scenes.Gone -> true
+ it.overlays.isNotEmpty() -> true
+ else -> false
+ }
},
SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED to
{
when {
- it.invisibleDueToOcclusion -> false
+ !it.isVisible -> false
it.scene == Scenes.Lockscreen -> true
it.scene == Scenes.Shade -> true
Overlays.NotificationsShade in it.overlays -> true
@@ -114,19 +120,23 @@ constructor(
},
SYSUI_STATE_QUICK_SETTINGS_EXPANDED to
{
- it.scene == Scenes.QuickSettings ||
- Overlays.QuickSettingsShade in it.overlays
+ when {
+ !it.isVisible -> false
+ it.scene == Scenes.QuickSettings -> true
+ Overlays.QuickSettingsShade in it.overlays -> true
+ else -> false
+ }
},
- SYSUI_STATE_BOUNCER_SHOWING to { Overlays.Bouncer in it.overlays },
+ SYSUI_STATE_BOUNCER_SHOWING to { it.isVisible && Overlays.Bouncer in it.overlays },
SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to
{
- it.scene == Scenes.Lockscreen && !it.invisibleDueToOcclusion
+ it.isVisible && it.scene == Scenes.Lockscreen
},
SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED to
{
it.scene == Scenes.Lockscreen && it.invisibleDueToOcclusion
},
- SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.scene == Scenes.Communal },
+ SYSUI_STATE_COMMUNAL_HUB_SHOWING to { it.isVisible && it.scene == Scenes.Communal },
)
}
@@ -134,5 +144,6 @@ constructor(
val scene: SceneKey,
val overlays: Set<OverlayKey>,
val invisibleDueToOcclusion: Boolean,
+ val isVisible: Boolean,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
index 1e18f24c9e65..195535669c7e 100644
--- a/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/model/SysUiStateExt.kt
@@ -16,8 +16,6 @@
package com.android.systemui.model
-import com.android.systemui.dagger.qualifiers.DisplayId
-
/**
* In-bulk updates multiple flag values and commits the update.
*
@@ -32,16 +30,8 @@ import com.android.systemui.dagger.qualifiers.DisplayId
* SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING to (sceneKey == Scenes.Lockscreen),
* )
* ```
- *
- * You can inject [displayId] by injecting it using:
- * ```
- * @DisplayId private val displayId: Int`,
- * ```
*/
-fun SysUiState.updateFlags(
- @DisplayId displayId: Int,
- vararg flagValuePairs: Pair<Long, Boolean>,
-) {
+fun SysUiState.updateFlags(vararg flagValuePairs: Pair<Long, Boolean>) {
flagValuePairs.forEach { (flag, enabled) -> setFlag(flag, enabled) }
- commitUpdate(displayId)
+ commitUpdate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
index 39482bea0111..c5c8c01f8b39 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java
@@ -16,17 +16,12 @@
package com.android.systemui.navigationbar;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
-
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import com.android.app.displaylib.PerDisplayRepository;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
@@ -34,8 +29,8 @@ import com.android.systemui.navigationbar.views.NavigationBarFrame;
import com.android.systemui.navigationbar.views.NavigationBarView;
import com.android.systemui.res.R;
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
-import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -70,8 +65,9 @@ public interface NavigationBarModule {
@Provides
@NavigationBarScope
@DisplayId
- static WindowManager provideWindowManager(@DisplayId Context context) {
- return context.getSystemService(WindowManager.class);
+ static WindowManager provideWindowManager(@DisplayId Context context,
+ WindowManagerProvider windowManagerProvider) {
+ return windowManagerProvider.getWindowManager(context);
}
/** A SysUiState for the navigation bar display. */
@@ -87,15 +83,4 @@ public interface NavigationBarModule {
return defaultState;
}
}
-
- /** A ViewCaptureAwareWindowManager specific to the display's context. */
- @Provides
- @NavigationBarScope
- @DisplayId
- static ViewCaptureAwareWindowManager provideViewCaptureAwareWindowManager(
- @DisplayId WindowManager windowManager, Lazy<ViewCapture> daggerLazyViewCapture) {
- return new ViewCaptureAwareWindowManager(windowManager,
- /* lazyViewCapture= */ toKotlinLazy(daggerLazyViewCapture),
- /* isViewCaptureEnabled= */ enableViewCaptureTracing());
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index 44c828731e24..6d7492686985 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -33,7 +33,6 @@ import androidx.annotation.VisibleForTesting
import androidx.core.os.postDelayed
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
@@ -85,7 +84,7 @@ class BackPanelController
@AssistedInject
constructor(
@Assisted context: Context,
- private val windowManager: ViewCaptureAwareWindowManager,
+ private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
@Assisted private val mainHandler: Handler,
private val systemClock: SystemClock,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 8b5b3adeef1f..ad0acbdaf702 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -104,7 +104,6 @@ import android.view.inputmethod.InputMethodManager;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -199,7 +198,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final Context mContext;
private final Bundle mSavedState;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final AccessibilityManager mAccessibilityManager;
private final DeviceProvisionedController mDeviceProvisionedController;
private final StatusBarStateController mStatusBarStateController;
@@ -560,7 +558,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
@Nullable Bundle savedState,
@DisplayId Context context,
@DisplayId WindowManager windowManager,
- @DisplayId ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
Lazy<AssistManager> assistManagerLazy,
AccessibilityManager accessibilityManager,
DeviceProvisionedController deviceProvisionedController,
@@ -605,7 +602,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mContext = context;
mSavedState = savedState;
mWindowManager = windowManager;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
mAccessibilityManager = accessibilityManager;
mDeviceProvisionedController = deviceProvisionedController;
mStatusBarStateController = statusBarStateController;
@@ -726,7 +722,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView);
try {
- mViewCaptureAwareWindowManager.addView(
+ mWindowManager.addView(
mFrame,
getBarLayoutParams(
mContext.getResources()
@@ -783,7 +779,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mCommandQueue.removeCallback(this);
Trace.beginSection("NavigationBar#removeViewImmediate");
try {
- mViewCaptureAwareWindowManager.removeViewImmediate(mView.getRootView());
+ mWindowManager.removeViewImmediate(mView.getRootView());
} catch (IllegalArgumentException e) {
// Wrapping this in a try/catch to avoid crashes when a display is instantly removed
// after being added, and initialization hasn't finished yet.
@@ -888,7 +884,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
resetSecondaryHandle();
getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
try {
- mViewCaptureAwareWindowManager.removeView(mOrientationHandle);
+ mWindowManager.removeView(mOrientationHandle);
} catch (IllegalArgumentException e) {
// Wrapping this in a try/catch to avoid crashes when a display is instantly removed
// after being added, and initialization hasn't finished yet.
@@ -967,7 +963,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
| WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
try {
- mViewCaptureAwareWindowManager.addView(mOrientationHandle, mOrientationParams);
+ mWindowManager.addView(mOrientationHandle, mOrientationParams);
} catch (WindowManager.InvalidDisplayException e) {
// Wrapping this in a try/catch to avoid crashes when a display is instantly removed
// after being added, and initialization hasn't finished yet.
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
index cbc4c26b2f94..d2974e9f90bd 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBarView.java
@@ -87,6 +87,7 @@ import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
+import com.android.systemui.utils.windowmanager.WindowManagerUtils;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
@@ -729,7 +730,7 @@ public class NavigationBarView extends FrameLayout {
} else {
return;
}
- WindowManager wm = getContext().getSystemService(WindowManager.class);
+ WindowManager wm = WindowManagerUtils.getWindowManager(getContext());
wm.updateViewLayout((View) getParent(), lp);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 8dc27bf4ac3e..080940169e46 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -86,7 +86,10 @@ constructor(
*/
private fun initializeKeyGestureEventHandler() {
if (useKeyGestureEventHandler()) {
- inputManager.registerKeyGestureEventHandler(callbacks)
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES),
+ callbacks,
+ )
}
}
@@ -156,11 +159,8 @@ constructor(
controller.updateNoteTaskForCurrentUserAndManagedProfiles()
}
- override fun handleKeyGestureEvent(
- event: KeyGestureEvent,
- focusedToken: IBinder?,
- ): Boolean {
- return this@NoteTaskInitializer.handleKeyGestureEvent(event)
+ override fun handleKeyGestureEvent(event: KeyGestureEvent, focusedToken: IBinder?) {
+ this@NoteTaskInitializer.handleKeyGestureEvent(event)
}
}
@@ -202,23 +202,19 @@ constructor(
return !isMultiPress && !isLongPress
}
- private fun handleKeyGestureEvent(event: KeyGestureEvent): Boolean {
- // This method is on input hot path and should be kept lightweight. Shift all complex
- // processing onto background executor wherever possible.
+ private fun handleKeyGestureEvent(event: KeyGestureEvent) {
if (event.keyGestureType != KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES) {
- return false
+ return
}
debugLog {
"handleKeyGestureEvent: Received OPEN_NOTES gesture event from keycodes: " +
event.keycodes.contentToString()
}
if (event.keycodes.size == 1 && event.keycodes[0] == KEYCODE_STYLUS_BUTTON_TAIL) {
- debugLog { "Note task triggered by stylus tail button" }
backgroundExecutor.execute { controller.showNoteTask(TAIL_BUTTON) }
- return true
+ } else {
+ backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
}
- backgroundExecutor.execute { controller.showNoteTask(KEYBOARD_SHORTCUT) }
- return true
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
index 9319961f5b68..0a5222e3df44 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayActionsViewModel.kt
@@ -21,8 +21,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
import com.android.systemui.scene.ui.viewmodel.UserActionsViewModel
@@ -38,11 +37,8 @@ class NotificationsShadeOverlayActionsViewModel @AssistedInject constructor() :
mapOf(
Swipe.Up to HideOverlay(Overlays.NotificationsShade),
Back to HideOverlay(Overlays.NotificationsShade),
- Swipe.Down(fromSource = SceneContainerArea.EndHalf) to
- ShowOverlay(
- Overlays.QuickSettingsShade,
- hideCurrentOverlays = HideCurrentOverlays.Some(Overlays.NotificationsShade),
- ),
+ Swipe.Down(fromSource = SceneContainerArea.TopEdgeEndHalf) to
+ ReplaceByOverlay(Overlays.QuickSettingsShade),
)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
index 2ecca2d8c776..f37a86e2b9d5 100644
--- a/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
+++ b/packages/SystemUI/src/com/android/systemui/power/InattentiveSleepWarningView.java
@@ -16,8 +16,6 @@
package com.android.systemui.power;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.animation.AnimatorListenerAdapter;
@@ -30,28 +28,21 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
-
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.res.R;
-import kotlin.Lazy;
-
/**
* View that shows a warning shortly before the device goes into sleep
* after prolonged user inactivity when bound to.
*/
public class InattentiveSleepWarningView extends FrameLayout {
private final IBinder mWindowToken = new Binder();
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private Animator mFadeOutAnimator;
private boolean mDismissing;
- InattentiveSleepWarningView(Context context, Lazy<ViewCapture> lazyViewCapture) {
+ InattentiveSleepWarningView(Context context, WindowManager windowManager) {
super(context);
- WindowManager wm = mContext.getSystemService(WindowManager.class);
- mWindowManager = new ViewCaptureAwareWindowManager(wm, lazyViewCapture,
- enableViewCaptureTracing());
+ mWindowManager = windowManager;
final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
layoutInflater.inflate(R.layout.inattentive_sleep_warning, this, true /* attachToRoot */);
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 861a7ce282af..4e1bfd15e58c 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -16,8 +16,6 @@
package com.android.systemui.power;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
-
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -42,11 +40,11 @@ import android.service.vr.IVrStateCallbacks;
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Slog;
+import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.app.viewcapture.ViewCapture;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.fuelgauge.Estimate;
import com.android.settingslib.utils.ThreadUtils;
@@ -59,8 +57,6 @@ import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import kotlin.Lazy;
-
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.concurrent.Future;
@@ -120,9 +116,9 @@ public class PowerUI implements
private IThermalEventListener mSkinThermalEventListener;
private IThermalEventListener mUsbThermalEventListener;
private final Context mContext;
+ private final WindowManager mWindowManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final CommandQueue mCommandQueue;
- private final Lazy<ViewCapture> mLazyViewCapture;
@Nullable
private final IVrManager mVrManager;
private final WakefulnessLifecycle.Observer mWakefulnessObserver =
@@ -164,7 +160,7 @@ public class PowerUI implements
WakefulnessLifecycle wakefulnessLifecycle,
PowerManager powerManager,
UserTracker userTracker,
- dagger.Lazy<ViewCapture> daggerLazyViewCapture) {
+ WindowManager windowManager) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mCommandQueue = commandQueue;
@@ -174,7 +170,7 @@ public class PowerUI implements
mPowerManager = powerManager;
mWakefulnessLifecycle = wakefulnessLifecycle;
mUserTracker = userTracker;
- mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
+ mWindowManager = windowManager;
}
public void start() {
@@ -649,7 +645,7 @@ public class PowerUI implements
@Override
public void showInattentiveSleepWarning() {
if (mOverlayView == null) {
- mOverlayView = new InattentiveSleepWarningView(mContext, mLazyViewCapture);
+ mOverlayView = new InattentiveSleepWarningView(mContext, mWindowManager);
}
mOverlayView.show();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
index b889c3e61837..9f04f69bd0bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -19,6 +19,8 @@ package com.android.systemui.qs.composefragment
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
+import android.graphics.Canvas
+import android.graphics.Path
import android.graphics.PointF
import android.graphics.Rect
import android.os.Bundle
@@ -125,7 +127,6 @@ import com.android.systemui.qs.composefragment.SceneKeys.debugName
import com.android.systemui.qs.composefragment.SceneKeys.toIdleSceneKey
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams
-import com.android.systemui.qs.composefragment.ui.notificationScrimClip
import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings
import com.android.systemui.qs.composefragment.ui.toEditMode
import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
@@ -241,7 +242,7 @@ constructor(
FrameLayoutTouchPassthrough(
context,
{ notificationScrimClippingParams.isEnabled },
- { notificationScrimClippingParams.params.top },
+ snapshotFlow { notificationScrimClippingParams.params },
// Only allow scrolling when we are fully expanded. That way, we don't intercept
// swipes in lockscreen (when somehow QS is receiving touches).
{ (scrollState.canScrollForward && viewModel.isQsFullyExpanded) || isCustomizing },
@@ -276,11 +277,6 @@ constructor(
}
}
.graphicsLayer { alpha = viewModel.viewAlpha }
- .thenIf(notificationScrimClippingParams.isEnabled) {
- Modifier.notificationScrimClip {
- notificationScrimClippingParams.params
- }
- }
.thenIf(!Flags.notificationShadeBlur()) {
Modifier.offset {
IntOffset(
@@ -1061,17 +1057,75 @@ private const val EDIT_MODE_TIME_MILLIS = 500
private class FrameLayoutTouchPassthrough(
context: Context,
private val clippingEnabledProvider: () -> Boolean,
- private val clippingTopProvider: () -> Int,
+ private val clippingParams: Flow<NotificationScrimClipParams>,
private val canScrollForwardQs: () -> Boolean,
private val emitMotionEventForFalsing: () -> Unit,
) : FrameLayout(context) {
+
+ init {
+ repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ clippingParams.collect { currentClipParams = it }
+ }
+ }
+ }
+
+ private val currentClippingPath = Path()
+ private var lastWidth = -1
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private var currentClipParams = NotificationScrimClipParams()
+ set(value) {
+ if (field != value) {
+ field = value
+ updateClippingPath()
+ }
+ }
+
+ private fun updateClippingPath() {
+ currentClippingPath.rewind()
+ if (clippingEnabledProvider()) {
+ val right = width + currentClipParams.rightInset
+ val left = -currentClipParams.leftInset
+ val top = currentClipParams.top
+ val bottom = currentClipParams.bottom
+ currentClippingPath.addRoundRect(
+ left.toFloat(),
+ top.toFloat(),
+ right.toFloat(),
+ bottom.toFloat(),
+ currentClipParams.radius.toFloat(),
+ currentClipParams.radius.toFloat(),
+ Path.Direction.CW,
+ )
+ }
+ invalidate()
+ }
+
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
+ super.onLayout(changed, left, top, right, bottom)
+ lastWidth = right - left
+ }
+
+ override fun dispatchDraw(canvas: Canvas) {
+ if (!currentClippingPath.isEmpty) {
+ canvas.clipOutPath(currentClippingPath)
+ }
+ super.dispatchDraw(canvas)
+ }
+
override fun isTransformedTouchPointInView(
x: Float,
y: Float,
child: View?,
outLocalPoint: PointF?,
): Boolean {
- return if (clippingEnabledProvider() && y + translationY > clippingTopProvider()) {
+ return if (clippingEnabledProvider() && y + translationY > currentClipParams.top) {
false
} else {
super.isTransformedTouchPointInView(x, y, child, outLocalPoint)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
deleted file mode 100644
index 3049a40f18c4..000000000000
--- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClip.kt
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.composefragment.ui
-
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawWithContent
-import androidx.compose.ui.geometry.CornerRadius
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.graphics.BlendMode
-import androidx.compose.ui.graphics.ClipOp
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.CompositingStrategy
-import androidx.compose.ui.graphics.drawscope.clipRect
-import androidx.compose.ui.graphics.graphicsLayer
-
-/**
- * Clipping modifier for clipping out the notification scrim as it slides over QS. It will clip out
- * ([ClipOp.Difference]) a `RoundRect(-leftInset, top, width + rightInset, bottom, radius, radius)`
- * from the QS container.
- */
-fun Modifier.notificationScrimClip(clipParams: () -> NotificationScrimClipParams): Modifier {
- return this.graphicsLayer { compositingStrategy = CompositingStrategy.Offscreen }
- .drawWithContent {
- drawContent()
- val params = clipParams()
- val left = -params.leftInset.toFloat()
- val right = size.width + params.rightInset.toFloat()
- val top = params.top.toFloat()
- val bottom = params.bottom.toFloat()
- val clipSize = Size(right - left, bottom - top)
- if (!clipSize.isEmpty()) {
- clipRect {
- drawRoundRect(
- color = Color.Black,
- cornerRadius = CornerRadius(params.radius.toFloat()),
- blendMode = BlendMode.Clear,
- topLeft = Offset(left, top),
- size = Size(right - left, bottom - top),
- )
- }
- }
- }
-}
-
-/** Params for [notificationScrimClip]. */
-data class NotificationScrimClipParams(
- val top: Int = 0,
- val bottom: Int = 0,
- val leftInset: Int = 0,
- val rightInset: Int = 0,
- val radius: Int = 0,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
new file mode 100644
index 000000000000..db320d3b9f1c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/NotificationScrimClipParams.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.ui
+
+/** Params for [notificationScrimClip]. */
+data class NotificationScrimClipParams(
+ val top: Int = 0,
+ val bottom: Int = 0,
+ val leftInset: Int = 0,
+ val rightInset: Int = 0,
+ val radius: Int = 0,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 405ce8a8e5e0..005c8b26b0d8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -35,7 +35,6 @@ import androidx.compose.ui.draganddrop.mimeTypes
import androidx.compose.ui.draganddrop.toAndroidDragEvent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.IntRect
-import androidx.compose.ui.unit.center
import androidx.compose.ui.unit.toRect
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
@@ -44,6 +43,7 @@ import com.android.systemui.qs.pipeline.shared.TileSpec
/** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */
interface DragAndDropState {
val draggedCell: SizedTile<EditTileViewModel>?
+ val isDraggedCellRemovable: Boolean
val draggedPosition: Offset
val dragInProgress: Boolean
val dragType: DragType?
@@ -76,7 +76,7 @@ enum class DragType {
@Composable
fun Modifier.dragAndDropRemoveZone(
dragAndDropState: DragAndDropState,
- onDrop: (TileSpec) -> Unit,
+ onDrop: (TileSpec, removalEnabled: Boolean) -> Unit,
): Modifier {
val target =
remember(dragAndDropState) {
@@ -87,13 +87,15 @@ fun Modifier.dragAndDropRemoveZone(
override fun onDrop(event: DragAndDropEvent): Boolean {
return dragAndDropState.draggedCell?.let {
- onDrop(it.tile.tileSpec)
+ onDrop(it.tile.tileSpec, dragAndDropState.isDraggedCellRemovable)
dragAndDropState.onDrop()
true
} ?: false
}
override fun onEntered(event: DragAndDropEvent) {
+ if (!dragAndDropState.isDraggedCellRemovable) return
+
dragAndDropState.movedOutOfBounds()
}
}
@@ -168,10 +170,10 @@ private fun DragAndDropEvent.toOffset(): Offset {
}
private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
- // We want to insert the tile after the target if we're aiming at the right side of a large tile
+ // We want to insert the tile after the target if we're aiming at the end of a large tile
// TODO(ostonge): Verify this behavior in RTL
- val itemCenter = item.offset + item.size.center
- return item.span != 1 && offset.x > itemCenter.x
+ val itemCenter = item.offset.x + item.size.width * .75
+ return item.span != 1 && offset.x > itemCenter
}
@OptIn(ExperimentalFoundationApi::class)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index 868855840922..70f1674acd3b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.geometry.Offset
import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.ui.compose.selection.PlacementEvent
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
import com.android.systemui.qs.panels.ui.model.toGridCells
@@ -60,6 +61,11 @@ class EditTileListState(
override var dragType by mutableStateOf<DragType?>(null)
private set
+ // A dragged cell can be removed if it was added in the drag movement OR if it's marked as
+ // removable
+ override val isDraggedCellRemovable: Boolean
+ get() = dragType == DragType.Add || draggedCell?.tile?.isRemovable ?: false
+
override val dragInProgress: Boolean
get() = draggedCell != null
@@ -76,10 +82,16 @@ class EditTileListState(
return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
}
+ fun isRemovable(tileSpec: TileSpec): Boolean {
+ return _tiles.find {
+ it is TileGridCell && it.tile.tileSpec == tileSpec && it.tile.isRemovable
+ } != null
+ }
+
/** Resize the tile corresponding to the [TileSpec] to [toIcon] */
fun resizeTile(tileSpec: TileSpec, toIcon: Boolean) {
val fromIndex = indexOf(tileSpec)
- if (fromIndex != -1) {
+ if (fromIndex != INVALID_INDEX) {
val cell = _tiles[fromIndex] as TileGridCell
if (cell.isIcon == toIcon) return
@@ -97,9 +109,6 @@ class EditTileListState(
override fun onStarted(cell: SizedTile<EditTileViewModel>, dragType: DragType) {
draggedCell = cell
this.dragType = dragType
-
- // Add spacers to the grid to indicate where the user can move a tile
- regenerateGrid()
}
override fun onTargeting(target: Int, insertAfter: Boolean) {
@@ -111,7 +120,7 @@ class EditTileListState(
}
val insertionIndex = if (insertAfter) target + 1 else target
- if (fromIndex != -1) {
+ if (fromIndex != INVALID_INDEX) {
val cell = _tiles.removeAt(fromIndex)
regenerateGrid()
_tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
@@ -149,6 +158,43 @@ class EditTileListState(
regenerateGrid()
}
+ /**
+ * Return the appropriate index to move the tile to for the placement [event]
+ *
+ * The grid includes spacers. As a result, indexes from the grid need to be translated to the
+ * corresponding index from [currentTileSpecs].
+ */
+ fun targetIndexForPlacement(event: PlacementEvent): Int {
+ val currentTileSpecs = tileSpecs()
+ return when (event) {
+ is PlacementEvent.PlaceToTileSpec -> {
+ currentTileSpecs.indexOf(event.targetSpec)
+ }
+ is PlacementEvent.PlaceToIndex -> {
+ if (event.targetIndex >= _tiles.size) {
+ currentTileSpecs.size
+ } else if (event.targetIndex <= 0) {
+ 0
+ } else {
+ // The index may point to a spacer, so first find the first tile located
+ // after index, then use its position as a target
+ val targetTile =
+ _tiles.subList(event.targetIndex, _tiles.size).firstOrNull {
+ it is TileGridCell
+ } as? TileGridCell
+
+ if (targetTile == null) {
+ currentTileSpecs.size
+ } else {
+ val targetIndex = currentTileSpecs.indexOf(targetTile.tile.tileSpec)
+ val fromIndex = currentTileSpecs.indexOf(event.movingSpec)
+ if (fromIndex < targetIndex) targetIndex - 1 else targetIndex
+ }
+ }
+ }
+ }
+ }
+
/** Regenerate the list of [GridCell] with their new potential rows */
private fun regenerateGrid() {
_tiles.filterIsInstance<TileGridCell>().toGridCells(columns).let {
@@ -170,4 +216,8 @@ class EditTileListState(
_tiles.addAll(it)
}
}
+
+ companion object {
+ const val INVALID_INDEX = -1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
index 46f05d0ac895..f8eaa6c3bcfb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt
@@ -19,6 +19,7 @@
package com.android.systemui.qs.panels.ui.compose.infinitegrid
import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.animateDpAsState
@@ -34,6 +35,7 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.clipScrollableContainer
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement.spacedBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -78,6 +80,7 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
@@ -96,6 +99,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.isSpecified
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.MeasureScope
import androidx.compose.ui.layout.layout
import androidx.compose.ui.layout.onGloballyPositioned
@@ -125,6 +129,7 @@ import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.DragAndDropState
import com.android.systemui.qs.panels.ui.compose.DragType
import com.android.systemui.qs.panels.ui.compose.EditTileListState
+import com.android.systemui.qs.panels.ui.compose.EditTileListState.Companion.INVALID_INDEX
import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone
import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList
import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource
@@ -152,7 +157,6 @@ import com.android.systemui.qs.panels.ui.model.AvailableTileGridCell
import com.android.systemui.qs.panels.ui.model.GridCell
import com.android.systemui.qs.panels.ui.model.SpacerGridCell
import com.android.systemui.qs.panels.ui.model.TileGridCell
-import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -161,7 +165,6 @@ import com.android.systemui.res.R
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
object TileType
@@ -225,7 +228,7 @@ fun DefaultEditTileGrid(
columns: Int,
largeTilesSpan: Int,
modifier: Modifier,
- onAddTile: (TileSpec) -> Unit,
+ onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
onSetTiles: (List<TileSpec>) -> Unit,
onResize: (TileSpec, toIcon: Boolean) -> Unit,
@@ -243,6 +246,15 @@ fun DefaultEditTileGrid(
null
}
+ LaunchedEffect(selectionState.placementEvent) {
+ selectionState.placementEvent?.let { event ->
+ listState
+ .targetIndexForPlacement(event)
+ .takeIf { it != INVALID_INDEX }
+ ?.let { onAddTile(event.movingSpec, it) }
+ }
+ }
+
Scaffold(
containerColor = Color.Transparent,
topBar = { EditModeTopBar(onStopEditing = onStopEditing, onReset = reset) },
@@ -272,29 +284,23 @@ fun DefaultEditTileGrid(
.padding(top = innerPadding.calculateTopPadding())
.clipScrollableContainer(Orientation.Vertical)
.verticalScroll(scrollState)
- .dragAndDropRemoveZone(listState, onRemoveTile),
+ .dragAndDropRemoveZone(listState) { spec, removalEnabled ->
+ if (removalEnabled) {
+ // If removal is enabled, remove the tile
+ onRemoveTile(spec)
+ } else {
+ // Otherwise submit the new tile ordering
+ onSetTiles(listState.tileSpecs())
+ selectionState.select(spec)
+ }
+ },
) {
- AnimatedContent(
- targetState = listState.dragInProgress || selectionState.selected,
- label = "QSEditHeader",
- contentAlignment = Alignment.Center,
+ CurrentTilesGridHeader(
+ listState,
+ selectionState,
+ onRemoveTile,
modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp),
- ) { showRemoveTarget ->
- EditGridHeader {
- if (showRemoveTarget) {
- RemoveTileTarget {
- selectionState.selection?.let {
- selectionState.unSelect()
- onRemoveTile(it)
- }
- }
- } else {
- EditGridCenteredText(
- text = stringResource(id = R.string.drag_to_rearrange_tiles)
- )
- }
- }
- }
+ )
CurrentTilesGrid(
listState,
@@ -315,7 +321,7 @@ fun DefaultEditTileGrid(
// Using the fully qualified name here as a workaround for AnimatedVisibility
// not being available from a Box
androidx.compose.animation.AnimatedVisibility(
- visible = !listState.dragInProgress,
+ visible = !listState.dragInProgress && !selectionState.placementEnabled,
enter = fadeIn(),
exit = fadeOut(),
) {
@@ -340,7 +346,7 @@ fun DefaultEditTileGrid(
availableTiles,
selectionState,
columns,
- onAddTile,
+ { onAddTile(it, listState.tileSpecs().size) }, // Add to the end
listState,
)
}
@@ -398,6 +404,76 @@ private fun AutoScrollGrid(
}
}
+private enum class EditModeHeaderState {
+ Remove,
+ Place,
+ Idle,
+}
+
+@Composable
+private fun rememberEditModeState(
+ listState: EditTileListState,
+ selectionState: MutableSelectionState,
+): State<EditModeHeaderState> {
+ val editGridHeaderState = remember { mutableStateOf(EditModeHeaderState.Idle) }
+ LaunchedEffect(
+ listState.dragInProgress,
+ selectionState.selected,
+ selectionState.placementEnabled,
+ ) {
+ val canRemove =
+ listState.isDraggedCellRemovable ||
+ selectionState.selection?.let { listState.isRemovable(it) } ?: false
+
+ editGridHeaderState.value =
+ when {
+ selectionState.placementEnabled -> EditModeHeaderState.Place
+ canRemove -> EditModeHeaderState.Remove
+ else -> EditModeHeaderState.Idle
+ }
+ }
+
+ return editGridHeaderState
+}
+
+@Composable
+private fun CurrentTilesGridHeader(
+ listState: EditTileListState,
+ selectionState: MutableSelectionState,
+ onRemoveTile: (TileSpec) -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val editGridHeaderState by rememberEditModeState(listState, selectionState)
+
+ AnimatedContent(
+ targetState = editGridHeaderState,
+ label = "QSEditHeader",
+ contentAlignment = Alignment.Center,
+ modifier = modifier,
+ ) { state ->
+ EditGridHeader {
+ when (state) {
+ EditModeHeaderState.Remove -> {
+ RemoveTileTarget {
+ selectionState.selection?.let {
+ selectionState.unSelect()
+ onRemoveTile(it)
+ }
+ }
+ }
+ EditModeHeaderState.Place -> {
+ EditGridCenteredText(text = stringResource(id = R.string.tap_to_position_tile))
+ }
+ EditModeHeaderState.Idle -> {
+ EditGridCenteredText(
+ text = stringResource(id = R.string.drag_to_rearrange_tiles)
+ )
+ }
+ }
+ }
+ }
+}
+
@Composable
private fun EditGridHeader(
modifier: Modifier = Modifier,
@@ -484,8 +560,14 @@ private fun CurrentTilesGrid(
}
.testTag(CURRENT_TILES_GRID_TEST_TAG),
) {
- EditTiles(cells, listState, selectionState, coroutineScope, largeTilesSpan, onRemoveTile) {
- resizingOperation ->
+ EditTiles(
+ cells,
+ listState,
+ selectionState,
+ coroutineScope,
+ largeTilesSpan,
+ onRemoveTile = onRemoveTile,
+ ) { resizingOperation ->
when (resizingOperation) {
is TemporaryResizeOperation -> {
currentListState.resizeTile(resizingOperation.spec, resizingOperation.toIcon)
@@ -585,6 +667,7 @@ private fun GridCell.key(index: Int): Any {
* @param selectionState the [MutableSelectionState] for this grid
* @param coroutineScope the [CoroutineScope] to be used for the tiles
* @param largeTilesSpan the width used for large tiles
+ * @param onRemoveTile the callback when a tile is removed from this grid
* @param onResize the callback when a tile has a new [ResizeOperation]
*/
fun LazyGridScope.EditTiles(
@@ -628,12 +711,33 @@ fun LazyGridScope.EditTiles(
modifier = Modifier.animateItem(),
)
}
- is SpacerGridCell -> SpacerGridCell()
+ is SpacerGridCell ->
+ SpacerGridCell(
+ Modifier.pointerInput(Unit) {
+ detectTapGestures(onTap = { selectionState.onTap(index) })
+ }
+ )
}
}
}
@Composable
+private fun rememberTileState(
+ tile: EditTileViewModel,
+ selectionState: MutableSelectionState,
+): State<TileState> {
+ val tileState = remember { mutableStateOf(TileState.None) }
+ val canShowRemovalBadge = tile.isRemovable
+
+ LaunchedEffect(selectionState.selection, selectionState.placementEnabled, canShowRemovalBadge) {
+ tileState.value =
+ selectionState.tileStateFor(tile.tileSpec, tileState.value, canShowRemovalBadge)
+ }
+
+ return tileState
+}
+
+@Composable
private fun TileGridCell(
cell: TileGridCell,
index: Int,
@@ -646,29 +750,7 @@ private fun TileGridCell(
modifier: Modifier = Modifier,
) {
val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
- val canShowRemovalBadge = cell.tile.availableEditActions.contains(AvailableEditActions.REMOVE)
- var tileState by remember { mutableStateOf(TileState.None) }
-
- LaunchedEffect(selectionState.selection, canShowRemovalBadge) {
- tileState =
- when {
- selectionState.selection == cell.tile.tileSpec -> {
- if (tileState == TileState.None && canShowRemovalBadge) {
- // The tile decoration is None if a tile is newly composed OR the removal
- // badge can't be shown.
- // For newly composed and selected tiles, such as dragged tiles or moved
- // tiles from resizing, introduce a short delay. This avoids clipping issues
- // on the border and resizing handle, as well as letting the selection
- // animation play correctly.
- delay(250)
- }
- TileState.Selected
- }
- canShowRemovalBadge -> TileState.Removable
- else -> TileState.None
- }
- }
-
+ val tileState by rememberTileState(cell.tile, selectionState)
val resizingState = rememberResizingState(cell.tile.tileSpec, cell.isIcon)
val progress: () -> Float = {
if (tileState == TileState.Selected) {
@@ -696,12 +778,16 @@ private fun TileGridCell(
with(LocalDensity.current) { (largeTilesSpan - 1) * TileArrangementPadding.roundToPx() }
val colors = EditModeTileDefaults.editTileColors()
val toggleSizeLabel = stringResource(R.string.accessibility_qs_edit_toggle_tile_size_action)
- val clickLabel =
+ val togglePlacementModeLabel =
+ stringResource(R.string.accessibility_qs_edit_toggle_placement_mode)
+ val decorationClickLabel =
when (tileState) {
- TileState.None -> null
TileState.Removable ->
stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
TileState.Selected -> toggleSizeLabel
+ TileState.None,
+ TileState.Placeable,
+ TileState.GreyedOut -> null
}
InteractiveTileContainer(
tileState = tileState,
@@ -720,8 +806,13 @@ private fun TileGridCell(
coroutineScope.launch { resizingState.toggleCurrentValue() }
}
},
- onClickLabel = clickLabel,
+ onClickLabel = decorationClickLabel,
) {
+ val placeableColor = MaterialTheme.colorScheme.primary.copy(alpha = .4f)
+ val backgroundColor by
+ animateColorAsState(
+ if (tileState == TileState.Placeable) placeableColor else colors.background
+ )
Box(
modifier
.fillMaxSize()
@@ -734,7 +825,11 @@ private fun TileGridCell(
CustomAccessibilityAction(toggleSizeLabel) {
onResize(FinalResizeOperation(cell.tile.tileSpec, !cell.isIcon))
true
- }
+ },
+ CustomAccessibilityAction(togglePlacementModeLabel) {
+ selectionState.togglePlacementMode(cell.tile.tileSpec)
+ true
+ },
)
}
.selectableTile(cell.tile.tileSpec, selectionState)
@@ -744,9 +839,14 @@ private fun TileGridCell(
DragType.Move,
selectionState::unSelect,
)
- .tileBackground(colors.background)
+ .tileBackground { backgroundColor }
) {
- EditTile(tile = cell.tile, state = resizingState, progress = progress)
+ EditTile(
+ tile = cell.tile,
+ tileState = tileState,
+ state = resizingState,
+ progress = progress,
+ )
}
}
}
@@ -791,7 +891,7 @@ private fun AvailableTileGridCell(
} else {
Modifier
}
- Box(draggableModifier.fillMaxSize().tileBackground(colors.background)) {
+ Box(draggableModifier.fillMaxSize().tileBackground { colors.background }) {
// Icon
SmallTileContent(
iconProvider = { cell.tile.icon },
@@ -834,11 +934,13 @@ private fun SpacerGridCell(modifier: Modifier = Modifier) {
@Composable
fun EditTile(
tile: EditTileViewModel,
+ tileState: TileState,
state: ResizingState,
progress: () -> Float,
colors: TileColors = EditModeTileDefaults.editTileColors(),
) {
val iconSizeDiff = CommonTileDefaults.IconSize - CommonTileDefaults.LargeTileIconSize
+ val alpha by animateFloatAsState(if (tileState == TileState.GreyedOut) .4f else 1f)
Row(
horizontalArrangement = spacedBy(6.dp),
verticalAlignment = Alignment.CenterVertically,
@@ -871,7 +973,8 @@ fun EditTile(
placeable.place(startPadding.roundToInt(), 0)
}
}
- .largeTilePadding(),
+ .largeTilePadding()
+ .graphicsLayer { this.alpha = alpha },
) {
// Icon
Box(Modifier.size(ToggleTargetSize)) {
@@ -889,7 +992,7 @@ fun EditTile(
label = tile.label.text,
secondaryLabel = tile.appName?.text,
colors = colors,
- modifier = Modifier.weight(1f).graphicsLayer { alpha = progress() },
+ modifier = Modifier.weight(1f).graphicsLayer { this.alpha = progress() },
)
}
}
@@ -908,9 +1011,9 @@ private fun MeasureScope.iconHorizontalCenter(containerSize: Int): Float {
CommonTileDefaults.TileStartPadding.toPx()
}
-private fun Modifier.tileBackground(color: Color): Modifier {
+private fun Modifier.tileBackground(color: () -> Color): Modifier {
// Clip tile contents from overflowing past the tile
- return clip(RoundedCornerShape(InactiveCornerRadius)).drawBehind { drawRect(color) }
+ return clip(RoundedCornerShape(InactiveCornerRadius)).drawBehind { drawRect(color()) }
}
private object EditModeTileDefaults {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
index 984343a45797..233af548fff2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt
@@ -42,7 +42,6 @@ import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel
import com.android.systemui.qs.panels.ui.viewmodel.InfiniteGridViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
-import com.android.systemui.qs.pipeline.domain.interactor.CurrentTilesInteractor.Companion.POSITION_AT_END
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey
import com.android.systemui.res.R
@@ -171,7 +170,7 @@ constructor(
otherTiles = otherTiles,
columns = columns,
modifier = modifier,
- onAddTile = { onAddTile(it, POSITION_AT_END) },
+ onAddTile = onAddTile,
onRemoveTile = onRemoveTile,
onSetTiles = onSetTiles,
onResize = iconTilesViewModel::resize,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
index 3dfde86bf8d9..50b29557683d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt
@@ -16,15 +16,17 @@
package com.android.systemui.qs.panels.ui.compose.selection
-import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
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.input.pointer.pointerInput
+import com.android.systemui.common.ui.compose.gestures.detectEagerTapGestures
import com.android.systemui.qs.pipeline.shared.TileSpec
+import kotlinx.coroutines.delay
/** Creates the state of the current selected tile that is remembered across compositions. */
@Composable
@@ -38,6 +40,17 @@ class MutableSelectionState {
var selection by mutableStateOf<TileSpec?>(null)
private set
+ /**
+ * Whether the current selection is in placement mode or not.
+ *
+ * A tile in placement mode can be positioned by tapping at the desired location in the grid.
+ */
+ var placementEnabled by mutableStateOf(false)
+ private set
+
+ /** Latest event from coming from placement mode. */
+ var placementEvent by mutableStateOf<PlacementEvent?>(null)
+
val selected: Boolean
get() = selection != null
@@ -47,37 +60,122 @@ class MutableSelectionState {
fun unSelect() {
selection = null
+ exitPlacementMode()
}
-}
-/**
- * Listens for click events to select/unselect the given [TileSpec]. Use this on current tiles as
- * they can be selected.
- */
-fun Modifier.selectableTile(
- tileSpec: TileSpec,
- selectionState: MutableSelectionState,
- onClick: () -> Unit = {},
-): Modifier {
- return pointerInput(Unit) {
- detectTapGestures(
- onTap = {
- if (selectionState.selection == tileSpec) {
- selectionState.unSelect()
- } else {
- selectionState.select(tileSpec)
+ /** Selects [tileSpec] and enable placement mode. */
+ fun enterPlacementMode(tileSpec: TileSpec) {
+ selection = tileSpec
+ placementEnabled = true
+ }
+
+ /** Disable placement mode but maintains current selection. */
+ private fun exitPlacementMode() {
+ placementEnabled = false
+ }
+
+ fun togglePlacementMode(tileSpec: TileSpec) {
+ if (placementEnabled) exitPlacementMode() else enterPlacementMode(tileSpec)
+ }
+
+ suspend fun tileStateFor(
+ tileSpec: TileSpec,
+ previousState: TileState,
+ canShowRemovalBadge: Boolean,
+ ): TileState {
+ return when {
+ placementEnabled && selection == tileSpec -> TileState.Placeable
+ placementEnabled -> TileState.GreyedOut
+ selection == tileSpec -> {
+ if (previousState == TileState.None && canShowRemovalBadge) {
+ // The tile decoration is None if a tile is newly composed OR the removal
+ // badge can't be shown.
+ // For newly composed and selected tiles, such as dragged tiles or moved
+ // tiles from resizing, introduce a short delay. This avoids clipping issues
+ // on the border and resizing handle, as well as letting the selection
+ // animation play correctly.
+ delay(250)
}
- onClick()
+ TileState.Selected
}
- )
+ canShowRemovalBadge -> TileState.Removable
+ else -> TileState.None
+ }
+ }
+
+ /**
+ * Tap callback on a tile.
+ *
+ * Tiles can be selected and placed using placement mode.
+ */
+ fun onTap(tileSpec: TileSpec) {
+ when {
+ placementEnabled && selection == tileSpec -> {
+ exitPlacementMode()
+ }
+ placementEnabled -> {
+ selection?.let { placementEvent = PlacementEvent.PlaceToTileSpec(it, tileSpec) }
+ exitPlacementMode()
+ }
+ selection == tileSpec -> {
+ unSelect()
+ }
+ else -> {
+ select(tileSpec)
+ }
+ }
+ }
+
+ /**
+ * Tap on a position.
+ *
+ * Use on grid items not associated with a [TileSpec], such as a spacer. Spacers can't be
+ * selected, but selections can be moved to their position.
+ */
+ fun onTap(index: Int) {
+ when {
+ placementEnabled -> {
+ selection?.let { placementEvent = PlacementEvent.PlaceToIndex(it, index) }
+ exitPlacementMode()
+ }
+ selected -> {
+ unSelect()
+ }
+ }
}
}
+// Not using data classes here as distinct placement events may have the same moving spec and target
+@Stable
+sealed interface PlacementEvent {
+ val movingSpec: TileSpec
+
+ /** Placement event corresponding to [movingSpec] moving to [targetSpec]'s position */
+ class PlaceToTileSpec(override val movingSpec: TileSpec, val targetSpec: TileSpec) :
+ PlacementEvent
+
+ /** Placement event corresponding to [movingSpec] moving to [targetIndex] */
+ class PlaceToIndex(override val movingSpec: TileSpec, val targetIndex: Int) : PlacementEvent
+}
+
/**
- * Listens for click events to unselect any tile. Use this on available tiles as they can't be
- * selected.
+ * Listens for click events on selectable tiles.
+ *
+ * Use this on current tiles as they can be selected.
+ *
+ * @param tileSpec the [TileSpec] of the tile this modifier is applied to
+ * @param selectionState the [MutableSelectionState] representing the grid's selection
*/
@Composable
-fun Modifier.clearSelectionTile(selectionState: MutableSelectionState): Modifier {
- return pointerInput(Unit) { detectTapGestures(onTap = { selectionState.unSelect() }) }
+fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier {
+ return pointerInput(Unit) {
+ detectEagerTapGestures(
+ doubleTapEnabled = {
+ // Double tap enabled if where not in placement mode already
+ !selectionState.placementEnabled
+ },
+ onDoubleTap = { selectionState.enterPlacementMode(tileSpec) },
+ onTap = { selectionState.onTap(tileSpec) },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
index 57f63c755b43..8ffc4be88e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.panels.ui.compose.selection
import androidx.compose.animation.animateColor
+import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.animateFloatAsState
@@ -37,9 +38,13 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
@@ -73,7 +78,9 @@ import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.RES
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingPillHeight
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingPillWidth
import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.GreyedOut
import com.android.systemui.qs.panels.ui.compose.selection.TileState.None
+import com.android.systemui.qs.panels.ui.compose.selection.TileState.Placeable
import com.android.systemui.qs.panels.ui.compose.selection.TileState.Removable
import com.android.systemui.qs.panels.ui.compose.selection.TileState.Selected
import kotlin.math.cos
@@ -104,10 +111,11 @@ fun InteractiveTileContainer(
) {
val transition: Transition<TileState> = updateTransition(tileState)
val decorationColor by transition.animateColor()
- val decorationAngle by transition.animateAngle()
+ val decorationAngle by animateAngle(tileState)
val decorationSize by transition.animateSize()
val decorationOffset by transition.animateOffset()
- val decorationAlpha by transition.animateFloat { state -> if (state == None) 0f else 1f }
+ val decorationAlpha by
+ transition.animateFloat { state -> if (state == Removable || state == Selected) 1f else 0f }
val badgeIconAlpha by transition.animateFloat { state -> if (state == Removable) 1f else 0f }
val selectionBorderAlpha by
transition.animateFloat { state -> if (state == Selected) 1f else 0f }
@@ -282,27 +290,61 @@ private fun Modifier.resizable(selected: Boolean, state: ResizingState): Modifie
}
enum class TileState {
+ /** Tile is displayed as-is, no additional decoration needed. */
None,
+ /** Tile can be removed by the user. This is displayed by a badge in the upper end corner. */
Removable,
+ /**
+ * Tile is selected and resizable. One tile can be selected at a time in the grid. This is when
+ * we display the resizing handle and a highlighted border around the tile.
+ */
Selected,
+ /**
+ * Tile placeable. This state means that the grid is in placement mode and this tile is
+ * selected. It should be highlighted to stand out in the grid.
+ */
+ Placeable,
+ /**
+ * Tile is faded out. This state means that the grid is in placement mode and this tile isn't
+ * selected. It serves as a target to place the selected tile.
+ */
+ GreyedOut,
}
@Composable
private fun Transition<TileState>.animateColor(): State<Color> {
return animateColor { state ->
when (state) {
- None -> Color.Transparent
+ None,
+ GreyedOut -> Color.Transparent
Removable -> MaterialTheme.colorScheme.primaryContainer
- Selected -> MaterialTheme.colorScheme.primary
+ Selected,
+ Placeable -> MaterialTheme.colorScheme.primary
}
}
}
+/**
+ * Animate the angle of the tile decoration based on the previous state
+ *
+ * Some [TileState] don't have a visible decoration, and the angle should only animate when going
+ * between visible states.
+ */
@Composable
-private fun Transition<TileState>.animateAngle(): State<Float> {
- return animateFloat { state ->
- if (state == Removable) BADGE_ANGLE_RAD else RESIZING_PILL_ANGLE_RAD
+private fun animateAngle(tileState: TileState): State<Float> {
+ val animatable = remember { Animatable(0f) }
+ var animate by remember { mutableStateOf(false) }
+ LaunchedEffect(tileState) {
+ val targetAngle = tileState.decorationAngle()
+
+ if (targetAngle == null) {
+ animate = false
+ } else {
+ if (animate) animatable.animateTo(targetAngle) else animatable.snapTo(targetAngle)
+ animate = true
+ }
}
+ return animatable.asState()
}
@Composable
@@ -310,7 +352,9 @@ private fun Transition<TileState>.animateSize(): State<Size> {
return animateSize { state ->
with(LocalDensity.current) {
when (state) {
- None -> Size.Zero
+ None,
+ Placeable,
+ GreyedOut -> Size.Zero
Removable -> Size(BadgeSize.toPx())
Selected -> Size(ResizingPillWidth.toPx(), ResizingPillHeight.toPx())
}
@@ -323,7 +367,9 @@ private fun Transition<TileState>.animateOffset(): State<Offset> {
return animateOffset { state ->
with(LocalDensity.current) {
when (state) {
- None -> Offset.Zero
+ None,
+ Placeable,
+ GreyedOut -> Offset.Zero
Removable -> Offset(BadgeXOffset.toPx(), BadgeYOffset.toPx())
Selected -> Offset(-SelectedBorderWidth.toPx(), 0f)
}
@@ -331,6 +377,16 @@ private fun Transition<TileState>.animateOffset(): State<Offset> {
}
}
+private fun TileState.decorationAngle(): Float? {
+ return when (this) {
+ Removable -> BADGE_ANGLE_RAD
+ Selected -> RESIZING_PILL_ANGLE_RAD
+ None,
+ Placeable,
+ GreyedOut -> null // No visible decoration
+ }
+}
+
private fun Size(size: Float) = Size(size, size)
private fun offsetForAngle(angle: Float, radius: Float, center: Offset): Offset {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
index be6ce5c5b4f4..cf325f531c38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
@@ -66,6 +66,9 @@ data class EditTileViewModel(
) : CategoryAndName {
override val name
get() = label.text
+
+ val isRemovable
+ get() = availableEditActions.contains(AvailableEditActions.REMOVE)
}
enum class AvailableEditActions {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt
index 43e3de2040d7..0236256ec9ae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesDndTile.kt
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.asQSTileIcon
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.base.shared.model.QSTileConfigProvider
@@ -88,7 +89,11 @@ constructor(
init {
lifecycle.coroutineScope.launch {
- lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ lifecycle.repeatOnLifecycle(
+ // TODO: b/403434908 - Workaround for "not listening to tile updates". Can be reset
+ // to RESUMED if either b/403434908 is fixed or QsInCompose is inlined.
+ if (QsInCompose.isEnabled) Lifecycle.State.RESUMED else Lifecycle.State.CREATED
+ ) {
dataInteractor.tileData().collect { refreshState(it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index fcfa46f07ab4..6a842c31a3aa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -31,7 +31,6 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.modes.shared.ModesUi
-import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QSTile
@@ -40,6 +39,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.asQSTileIcon
+import com.android.systemui.qs.flags.QsInCompose
import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.qs.tiles.base.shared.model.QSTileConfigProvider
@@ -88,10 +88,12 @@ constructor(
private val config = qsTileConfigProvider.getConfig(TILE_SPEC)
init {
- /* Check if */ ModesUiIcons.isUnexpectedlyInLegacyMode()
-
lifecycle.coroutineScope.launch {
- lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ lifecycle.repeatOnLifecycle(
+ // TODO: b/403434908 - Workaround for "not listening to tile updates". Can be reset
+ // to RESUMED if either b/403434908 is fixed or QsInCompose is inlined.
+ if (QsInCompose.isEnabled) Lifecycle.State.RESUMED else Lifecycle.State.CREATED
+ ) {
dataInteractor.tileData().collect { refreshState(it) }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
index 6b5a22a4fc09..945e051606b9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
@@ -65,7 +65,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -186,7 +185,7 @@ public class InternetDetailsContentController implements AccessPointController.A
private GlobalSettings mGlobalSettings;
private int mDefaultDataSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private ConnectivityManager.NetworkCallback mConnectivityManagerNetworkCallback;
- private ViewCaptureAwareWindowManager mWindowManager;
+ private WindowManager mWindowManager;
private ToastFactory mToastFactory;
private SignalDrawable mSignalDrawable;
private SignalDrawable mSecondarySignalDrawable; // For the secondary mobile data sub in DSDS
@@ -252,7 +251,7 @@ public class InternetDetailsContentController implements AccessPointController.A
@Main Handler handler, @Main Executor mainExecutor,
BroadcastDispatcher broadcastDispatcher, KeyguardUpdateMonitor keyguardUpdateMonitor,
GlobalSettings globalSettings, KeyguardStateController keyguardStateController,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager, ToastFactory toastFactory,
+ @ShadeDisplayAware WindowManager windowManager, ToastFactory toastFactory,
@Background Handler workerHandler,
CarrierConfigTracker carrierConfigTracker,
LocationController locationController,
@@ -284,7 +283,7 @@ public class InternetDetailsContentController implements AccessPointController.A
mAccessPointController = accessPointController;
mWifiIconInjector = new WifiUtils.InternetIconInjector(mContext);
mConnectivityManagerNetworkCallback = new DataConnectivityListener();
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mToastFactory = toastFactory;
mSignalDrawable = new SignalDrawable(mContext);
mSecondarySignalDrawable = new SignalDrawable(mContext);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
index f9b1a36621b2..11622be3d417 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsContainerViewModel.kt
@@ -77,7 +77,7 @@ constructor(
val showMedia: Boolean by
hydrator.hydratedStateOf(
traceName = "showMedia",
- source = mediaCarouselInteractor.hasActiveMediaOrRecommendation,
+ source = mediaCarouselInteractor.hasAnyMediaOrRecommendation,
)
override suspend fun onActivated(): Nothing {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
index 52c4e2fac6d5..2f7bc0992411 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayActionsViewModel.kt
@@ -21,8 +21,7 @@ import com.android.compose.animation.scene.Swipe
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.UserActionResult.HideOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay
-import com.android.compose.animation.scene.UserActionResult.ShowOverlay.HideCurrentOverlays
+import com.android.compose.animation.scene.UserActionResult.ReplaceByOverlay
import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.viewmodel.SceneContainerArea
@@ -47,12 +46,8 @@ constructor(private val editModeViewModel: EditModeViewModel) : UserActionsViewM
put(Back, HideOverlay(Overlays.QuickSettingsShade))
}
put(
- Swipe.Down(fromSource = SceneContainerArea.StartHalf),
- ShowOverlay(
- Overlays.NotificationsShade,
- hideCurrentOverlays =
- HideCurrentOverlays.Some(Overlays.QuickSettingsShade),
- ),
+ Swipe.Down(fromSource = SceneContainerArea.TopEdgeStartHalf),
+ ReplaceByOverlay(Overlays.NotificationsShade),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
index 263ef09ea767..5d4a774d77f9 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayCoreStartable.kt
@@ -19,14 +19,18 @@ package com.android.systemui.reardisplay
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import android.hardware.devicestate.feature.flags.Flags
+import android.os.Handler
+import android.view.accessibility.AccessibilityManager
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.domain.interactor.RearDisplayStateInteractor
import com.android.systemui.statusbar.phone.SystemUIDialog
+import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
@@ -52,6 +56,8 @@ internal constructor(
private val rearDisplayInnerDialogDelegateFactory: RearDisplayInnerDialogDelegate.Factory,
@Application private val scope: CoroutineScope,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val accessibilityManager: AccessibilityManager,
+ @Background private val handler: Handler,
) : CoreStartable, AutoCloseable {
companion object {
@@ -77,6 +83,12 @@ internal constructor(
override fun start() {
if (Flags.deviceStateRdmV2()) {
var dialog: SystemUIDialog? = null
+ var touchExplorationEnabled = AtomicBoolean(false)
+
+ accessibilityManager.addTouchExplorationStateChangeListener(
+ { enabled -> touchExplorationEnabled.set(enabled) },
+ handler,
+ )
keyguardUpdateMonitor.registerCallback(keyguardCallback)
@@ -99,6 +111,7 @@ internal constructor(
rearDisplayInnerDialogDelegateFactory.create(
rearDisplayContext,
deviceStateManager::cancelStateRequest,
+ touchExplorationEnabled.get(),
)
dialog = delegate.createDialog().apply { show() }
}
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
index f5facf42ee67..96f1bd270239 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegate.kt
@@ -20,7 +20,10 @@ import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.MotionEvent
+import android.view.View
+import android.widget.Button
import android.widget.SeekBar
+import android.widget.TextView
import com.android.systemui.haptics.slider.HapticSlider
import com.android.systemui.haptics.slider.HapticSliderPlugin
import com.android.systemui.haptics.slider.HapticSliderViewBinder
@@ -45,6 +48,7 @@ class RearDisplayInnerDialogDelegate
internal constructor(
private val systemUIDialogFactory: SystemUIDialog.Factory,
@Assisted private val rearDisplayContext: Context,
+ @Assisted private val touchExplorationEnabled: Boolean,
private val vibratorHelper: VibratorHelper,
private val msdlPlayer: MSDLPlayer,
private val systemClock: SystemClock,
@@ -82,6 +86,7 @@ internal constructor(
fun create(
rearDisplayContext: Context,
onCanceledRunnable: Runnable,
+ touchExplorationEnabled: Boolean,
): RearDisplayInnerDialogDelegate
}
@@ -95,11 +100,32 @@ internal constructor(
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+
dialog.apply {
setContentView(R.layout.activity_rear_display_enabled)
setCanceledOnTouchOutside(false)
+ requireViewById<Button>(R.id.cancel_button).let { it ->
+ if (!touchExplorationEnabled) {
+ return@let
+ }
+
+ it.visibility = View.VISIBLE
+ it.setOnClickListener { onCanceledRunnable.run() }
+ }
+
+ requireViewById<TextView>(R.id.seekbar_instructions).let { it ->
+ if (touchExplorationEnabled) {
+ it.visibility = View.GONE
+ }
+ }
+
requireViewById<SeekBar>(R.id.seekbar).let { it ->
+ if (touchExplorationEnabled) {
+ it.visibility = View.GONE
+ return@let
+ }
+
// Create and bind the HapticSliderPlugin
val hapticSliderPlugin =
HapticSliderPlugin(
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index 432a35a1a3dd..a1281ec23f92 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -16,9 +16,7 @@
package com.android.systemui.recents;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
@@ -55,8 +53,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
@@ -87,7 +83,6 @@ public class ScreenPinningRequest implements
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final AccessibilityManager mAccessibilityService;
private final WindowManager mWindowManager;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final UserTracker mUserTracker;
@@ -112,15 +107,12 @@ public class ScreenPinningRequest implements
Lazy<NavigationBarController> navigationBarControllerLazy,
BroadcastDispatcher broadcastDispatcher,
UserTracker userTracker,
- Lazy<ViewCapture> daggerLazyViewCapture) {
+ WindowManager windowManager) {
mContext = context;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mAccessibilityService = (AccessibilityManager)
mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- mWindowManager = (WindowManager)
- mContext.getSystemService(Context.WINDOW_SERVICE);
- mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
- toKotlinLazy(daggerLazyViewCapture), enableViewCaptureTracing());
+ mWindowManager = windowManager;
mNavBarMode = navigationModeController.addListener(this);
mBroadcastDispatcher = broadcastDispatcher;
mUserTracker = userTracker;
@@ -131,7 +123,7 @@ public class ScreenPinningRequest implements
public void clearPrompt() {
if (mRequestWindow != null) {
- mViewCaptureAwareWindowManager.removeView(mRequestWindow);
+ mWindowManager.removeView(mRequestWindow);
mRequestWindow = null;
}
}
@@ -152,7 +144,7 @@ public class ScreenPinningRequest implements
// show the confirmation
WindowManager.LayoutParams lp = getWindowLayoutParams();
- mViewCaptureAwareWindowManager.addView(mRequestWindow, lp);
+ mWindowManager.addView(mRequestWindow, lp);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 3ad0867192d3..06fc8610c97b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -33,10 +33,8 @@ import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.bouncer.shared.logging.BouncerUiEvent
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorActual
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.DisplayId
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -82,6 +80,7 @@ import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.printSection
import com.android.systemui.util.println
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.google.android.msdl.data.model.MSDLToken
import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
@@ -123,7 +122,6 @@ constructor(
private val bouncerInteractor: BouncerInteractor,
private val keyguardInteractor: KeyguardInteractor,
private val sysUiState: SysUiState,
- @DisplayId private val displayId: Int,
private val sceneLogger: SceneLogger,
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
@@ -197,7 +195,8 @@ constructor(
return
}
- printSection("Scene state") {
+ printSection("Framework state") {
+ println("isVisible", sceneInteractor.isVisible.value)
println("currentScene", sceneInteractor.currentScene.value.debugName)
println(
"currentOverlays",
@@ -732,21 +731,26 @@ constructor(
sceneInteractor.transitionState
.mapNotNull { it as? ObservableTransitionState.Idle }
.distinctUntilChanged(),
+ sceneInteractor.isVisible,
occlusionInteractor.invisibleDueToOcclusion,
- ) { idleState, invisibleDueToOcclusion ->
+ ) { idleState, isVisible, invisibleDueToOcclusion ->
SceneContainerPlugin.SceneContainerPluginState(
scene = idleState.currentScene,
overlays = idleState.currentOverlays,
+ isVisible = isVisible,
invisibleDueToOcclusion = invisibleDueToOcclusion,
)
}
- .collect { sceneContainerPluginState ->
+ .map { sceneContainerPluginState ->
+ SceneContainerPlugin.EvaluatorByFlag.map { (flag, evaluator) ->
+ flag to evaluator(sceneContainerPluginState)
+ }
+ .toMap()
+ }
+ .distinctUntilChanged()
+ .collect { flags ->
sysUiState.updateFlags(
- displayId,
- *SceneContainerPlugin.EvaluatorByFlag.map { (flag, evaluator) ->
- flag to evaluator.invoke(sceneContainerPluginState)
- }
- .toTypedArray(),
+ *(flags.entries.map { (key, value) -> key to value }).toTypedArray()
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt
index ede453dbe6b3..760b97e6245b 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerSwipeDetector.kt
@@ -58,6 +58,22 @@ sealed class SceneContainerArea(private val resolveArea: (LayoutDirection) -> Re
}
)
+ data object TopEdgeStartHalf :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.TopEdgeLeftHalf
+ else Resolved.TopEdgeRightHalf
+ }
+ )
+
+ data object TopEdgeEndHalf :
+ SceneContainerArea(
+ resolveArea = {
+ if (it == LayoutDirection.Ltr) Resolved.TopEdgeRightHalf
+ else Resolved.TopEdgeLeftHalf
+ }
+ )
+
override fun resolve(layoutDirection: LayoutDirection): Resolved {
return resolveArea(layoutDirection)
}
@@ -72,6 +88,12 @@ sealed class SceneContainerArea(private val resolveArea: (LayoutDirection) -> Re
data object RightEdge : Resolved
data object RightHalf : Resolved
+
+ /** The left half of the top edge of the display. */
+ data object TopEdgeLeftHalf : Resolved
+
+ /** The right half of the top edge of the display. */
+ data object TopEdgeRightHalf : Resolved
}
}
@@ -108,9 +130,14 @@ class SceneContainerSwipeDetector(val edgeSize: Dp) : SwipeSourceDetector {
Edge.Resolved.Left -> SceneContainerArea.Resolved.LeftEdge
Edge.Resolved.Bottom -> SceneContainerArea.Resolved.BottomEdge
Edge.Resolved.Right -> SceneContainerArea.Resolved.RightEdge
- else -> {
- // Note: This intentionally includes Edge.Resolved.Top. At the moment, we don't need
- // to detect swipes on the top edge, and consider them part of the right/left half.
+ Edge.Resolved.Top -> {
+ if (position.x < layoutSize.width * 0.5f) {
+ SceneContainerArea.Resolved.TopEdgeLeftHalf
+ } else {
+ SceneContainerArea.Resolved.TopEdgeRightHalf
+ }
+ }
+ null -> {
if (position.x < layoutSize.width * 0.5f) {
SceneContainerArea.Resolved.LeftHalf
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
index c4fe7a428084..644e12cba6fc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotWindow.kt
@@ -31,7 +31,6 @@ import android.view.Window
import android.view.WindowInsets
import android.view.WindowManager
import android.window.WindowContext
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.policy.PhoneWindow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -42,7 +41,6 @@ class ScreenshotWindow
@AssistedInject
constructor(
private val windowManager: WindowManager,
- private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
private val context: Context,
@Assisted private val display: Display,
) {
@@ -97,7 +95,7 @@ constructor(
Log.d(TAG, "attachWindow")
}
attachRequested = true
- viewCaptureAwareWindowManager.addView(decorView, params)
+ windowManager.addView(decorView, params)
decorView.requestApplyInsets()
decorView.requireViewById<ViewGroup>(R.id.content).apply {
@@ -135,7 +133,7 @@ constructor(
if (LogConfig.DEBUG_WINDOW) {
Log.d(TAG, "Removing screenshot window")
}
- viewCaptureAwareWindowManager.removeViewImmediate(decorView)
+ windowManager.removeViewImmediate(decorView)
detachRequested = false
}
if (attachRequested && !detachRequested) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 24e7976011f4..b90624245cc5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -206,6 +206,7 @@ import com.google.android.msdl.data.model.MSDLToken;
import com.google.android.msdl.domain.MSDLPlayer;
import dagger.Lazy;
+
import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
@@ -4267,7 +4268,8 @@ public final class NotificationPanelViewController implements
== AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
|| action
== AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true,
+ "NotificationPanelViewController#performAccessibilityAction");
return true;
}
return super.performAccessibilityAction(host, action, args);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index c671f7d9db14..4ad4ab3410d6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -102,6 +102,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
import com.android.systemui.util.LargeScreenUtils;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import dalvik.annotation.optimization.NeverCompile;
@@ -299,6 +300,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
private final Runnable mQsCollapseExpandAction = this::collapseOrExpandQs;
private final QS.ScrollListener mQsScrollListener = this::onScroll;
+ private final WindowManagerProvider mWindowManagerProvider;
+
@Inject
public QuickSettingsControllerImpl(
Lazy<NotificationPanelViewController> panelViewControllerLazy,
@@ -336,7 +339,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
CastController castController,
SplitShadeStateController splitShadeStateController,
Lazy<CommunalTransitionViewModel> communalTransitionViewModelLazy,
- Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy
+ Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy,
+ WindowManagerProvider windowManagerProvider
) {
SceneContainerFlag.assertInLegacyMode();
mPanelViewControllerLazy = panelViewControllerLazy;
@@ -387,6 +391,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
dumpManager.registerDumpable(this);
+
+ mWindowManagerProvider = windowManagerProvider;
}
@VisibleForTesting
@@ -532,7 +538,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
* on ACTION_DOWN, and safely queried repeatedly thereafter during ACTION_MOVE events.
*/
void updateGestureInsetsCache() {
- WindowManager wm = this.mPanelView.getContext().getSystemService(WindowManager.class);
+ WindowManager wm = mWindowManagerProvider.getWindowManager(this.mPanelView.getContext());
WindowMetrics windowMetrics = wm.getCurrentWindowMetrics();
mCachedGestureInsets = windowMetrics.getWindowInsets().getInsets(
WindowInsets.Type.systemGestures());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index e6834ad6cdda..b211f0729318 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -97,6 +97,11 @@ constructor(
loggingReason = "ShadeControllerSceneImpl.instantCollapseShade",
transitionKey = Instant,
)
+
+ shadeInteractor.collapseQuickSettingsShade(
+ loggingReason = "ShadeControllerSceneImpl.instantCollapseShade",
+ transitionKey = Instant,
+ )
}
override fun animateCollapseShade(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index cd224735cc62..446d4b450edc 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -49,6 +49,8 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackRebind
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.utils.windowmanager.WindowManagerProvider
+import com.android.systemui.utils.windowmanager.WindowManagerUtils
import dagger.Module
import dagger.Provides
import dagger.multibindings.ClassKey
@@ -111,9 +113,10 @@ object ShadeDisplayAwareModule {
fun provideShadeWindowManager(
defaultWindowManager: WindowManager,
@ShadeDisplayAware context: Context,
+ windowManagerProvider: WindowManagerProvider
): WindowManager {
return if (ShadeWindowGoesAround.isEnabled) {
- context.getSystemService(WindowManager::class.java) as WindowManager
+ windowManagerProvider.getWindowManager(context)
} else {
defaultWindowManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
index 52de0abf7d3c..043742245227 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorSceneContainerImpl.kt
@@ -187,12 +187,18 @@ constructor(
override fun collapseNotificationsShade(loggingReason: String, transitionKey: TransitionKey?) {
if (shadeModeInteractor.isDualShade) {
- // TODO(b/356596436): Hide without animation if transitionKey is Instant.
- sceneInteractor.hideOverlay(
- overlay = Overlays.NotificationsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
+ if (transitionKey == Instant) {
+ sceneInteractor.instantlyHideOverlay(
+ overlay = Overlays.NotificationsShade,
+ loggingReason = loggingReason,
+ )
+ } else {
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.NotificationsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ }
} else if (transitionKey == Instant) {
// TODO(b/356596436): Define instant transition instead of snapToScene().
sceneInteractor.snapToScene(
@@ -215,12 +221,18 @@ constructor(
bypassNotificationsShade: Boolean,
) {
if (shadeModeInteractor.isDualShade) {
- // TODO(b/356596436): Hide without animation if transitionKey is Instant.
- sceneInteractor.hideOverlay(
- overlay = Overlays.QuickSettingsShade,
- loggingReason = loggingReason,
- transitionKey = transitionKey,
- )
+ if (transitionKey == Instant) {
+ sceneInteractor.instantlyHideOverlay(
+ overlay = Overlays.QuickSettingsShade,
+ loggingReason = loggingReason,
+ )
+ } else {
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.QuickSettingsShade,
+ loggingReason = loggingReason,
+ transitionKey = transitionKey,
+ )
+ }
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 88242762da78..c264e3525026 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -276,15 +276,17 @@ constructor(
data object Weak : HeaderChipHighlight {
override fun backgroundColor(colorScheme: ColorScheme): Color =
- colorScheme.primary.copy(alpha = 0.1f)
+ colorScheme.surface.copy(alpha = 0.1f)
- override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.primary
+ override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSurface
}
data object Strong : HeaderChipHighlight {
- override fun backgroundColor(colorScheme: ColorScheme): Color = colorScheme.secondary
+ override fun backgroundColor(colorScheme: ColorScheme): Color =
+ colorScheme.primaryContainer
- override fun foregroundColor(colorScheme: ColorScheme): Color = colorScheme.onSecondary
+ override fun foregroundColor(colorScheme: ColorScheme): Color =
+ colorScheme.onPrimaryContainer
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
index cf3b08c041be..8d2d9efb56eb 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActions.kt
@@ -71,6 +71,8 @@ fun dualShadeActions(): Array<Pair<UserAction, UserActionResult>> {
Swipe.Down to ShowOverlay(Overlays.NotificationsShade),
Swipe.Down(fromSource = SceneContainerArea.EndHalf) to
ShowOverlay(Overlays.QuickSettingsShade),
+ Swipe.Down(fromSource = SceneContainerArea.TopEdgeEndHalf) to
+ ShowOverlay(Overlays.QuickSettingsShade),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index e44701dba87c..4daf61a895c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -64,6 +64,7 @@ import androidx.annotation.VisibleForTesting;
import com.android.internal.annotations.KeepForWeakReference;
import com.android.internal.os.SomeArgs;
+import com.android.internal.statusbar.DisableStates;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -85,6 +86,7 @@ import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Map;
/**
* This class takes the functions from IStatusBar that come in on
@@ -184,6 +186,8 @@ public class CommandQueue extends IStatusBar.Stub implements
private static final int MSG_TOGGLE_QUICK_SETTINGS_PANEL = 82 << MSG_SHIFT;
private static final int MSG_WALLET_ACTION_LAUNCH_GESTURE = 83 << MSG_SHIFT;
private static final int MSG_DISPLAY_REMOVE_SYSTEM_DECORATIONS = 85 << MSG_SHIFT;
+ private static final int MSG_DISABLE_ALL = 86 << MSG_SHIFT;
+
public static final int FLAG_EXCLUDE_NONE = 0;
public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
public static final int FLAG_EXCLUDE_RECENTS_PANEL = 1 << 1;
@@ -654,7 +658,8 @@ public class CommandQueue extends IStatusBar.Stub implements
/**
* Called to notify that disable flags are updated.
- * @see Callbacks#disable(int, int, int, boolean).
+ * @see Callbacks#disable(int, int, int, boolean)
+ * @see Callbacks#disableForAllDisplays(DisableStates)
*/
public void disable(int displayId, @DisableFlags int state1, @Disable2Flags int state2,
boolean animate) {
@@ -682,6 +687,27 @@ public class CommandQueue extends IStatusBar.Stub implements
disable(displayId, state1, state2, true);
}
+ @Override
+ public void disableForAllDisplays(DisableStates disableStates) throws RemoteException {
+ synchronized (mLock) {
+ for (Map.Entry<Integer, Pair<Integer, Integer>> displaysWithStates :
+ disableStates.displaysWithStates.entrySet()) {
+ int displayId = displaysWithStates.getKey();
+ Pair<Integer, Integer> states = displaysWithStates.getValue();
+ setDisabled(displayId, states.first, states.second);
+ }
+ mHandler.removeMessages(MSG_DISABLE_ALL);
+ Message msg = mHandler.obtainMessage(MSG_DISABLE_ALL, disableStates);
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ // If its the right looper execute immediately so hides can be handled quickly.
+ mHandler.handleMessage(msg);
+ msg.recycle();
+ } else {
+ msg.sendToTarget();
+ }
+ }
+ }
+
/**
* Apply current disable flags by {@link CommandQueue#disable(int, int, int, boolean)}.
*
@@ -1552,6 +1578,21 @@ public class CommandQueue extends IStatusBar.Stub implements
args.argi4 != 0 /* animate */);
}
break;
+ case MSG_DISABLE_ALL:
+ DisableStates disableStates = (DisableStates) msg.obj;
+ boolean animate = disableStates.animate;
+ Map<Integer, Pair<Integer, Integer>> displaysWithDisableStates =
+ disableStates.displaysWithStates;
+ for (Map.Entry<Integer, Pair<Integer, Integer>> displayWithDisableStates :
+ displaysWithDisableStates.entrySet()) {
+ int displayId = displayWithDisableStates.getKey();
+ Pair<Integer, Integer> states = displayWithDisableStates.getValue();
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).disable(displayId, states.first, states.second,
+ animate);
+ }
+ }
+ break;
case MSG_EXPAND_NOTIFICATIONS:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).animateExpandNotificationsPanel();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 97e62d79b374..2a9a47d83dd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -28,9 +28,6 @@ import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
import static android.window.DisplayAreaOrganizer.KEY_ROOT_DISPLAY_AREA_ID;
-import static com.android.systemui.Flags.enableViewCaptureTracing;
-import static com.android.systemui.util.ConvenienceExtensionsKt.toKotlinLazy;
-
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
@@ -76,16 +73,13 @@ import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.res.R;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.util.settings.SecureSettings;
-
-import kotlin.Lazy;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import javax.inject.Inject;
@@ -112,13 +106,14 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
private long mShowDelayMs = 0L;
private final IBinder mWindowToken = new Binder();
private final CommandQueue mCommandQueue;
+ private final WindowManagerProvider mWindowManagerProvider;
private ClingWindowView mClingWindow;
/** The wrapper on the last {@link WindowManager} used to add the confirmation window. */
@Nullable
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private WindowManager mWindowManager;
/**
- * The WindowContext that is registered with {@link #mViewCaptureAwareWindowManager} with
+ * The WindowContext that is registered with {@link #mWindowManager} with
* options to specify the {@link RootDisplayArea} to attach the confirmation window.
*/
@Nullable
@@ -136,21 +131,18 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
private ContentObserver mContentObserver;
- private Lazy<ViewCapture> mLazyViewCapture;
-
@Inject
public ImmersiveModeConfirmation(Context context, CommandQueue commandQueue,
- SecureSettings secureSettings,
- dagger.Lazy<ViewCapture> daggerLazyViewCapture,
- @Background Handler backgroundHandler) {
+ SecureSettings secureSettings, @Background Handler backgroundHandler,
+ WindowManagerProvider windowManagerProvider) {
mSysUiContext = context;
final Display display = mSysUiContext.getDisplay();
mDisplayContext = display.getDisplayId() == DEFAULT_DISPLAY
? mSysUiContext : mSysUiContext.createDisplayContext(display);
mCommandQueue = commandQueue;
mSecureSettings = secureSettings;
- mLazyViewCapture = toKotlinLazy(daggerLazyViewCapture);
mBackgroundHandler = backgroundHandler;
+ mWindowManagerProvider = windowManagerProvider;
}
boolean loadSetting(int currentUserId) {
@@ -257,14 +249,14 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
private void handleHide() {
if (mClingWindow != null) {
if (DEBUG) Log.d(TAG, "Hiding immersive mode confirmation");
- if (mViewCaptureAwareWindowManager != null) {
+ if (mWindowManager != null) {
try {
- mViewCaptureAwareWindowManager.removeView(mClingWindow);
+ mWindowManager.removeView(mClingWindow);
} catch (WindowManager.InvalidDisplayException e) {
Log.w(TAG, "Fail to hide the immersive confirmation window because of "
+ e);
}
- mViewCaptureAwareWindowManager = null;
+ mWindowManager = null;
mWindowContext = null;
}
mClingWindow = null;
@@ -525,8 +517,8 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
* confirmation window.
*/
@NonNull
- private ViewCaptureAwareWindowManager createWindowManager(int rootDisplayAreaId) {
- if (mViewCaptureAwareWindowManager != null) {
+ private WindowManager createWindowManager(int rootDisplayAreaId) {
+ if (mWindowManager != null) {
throw new IllegalStateException(
"Must not create a new WindowManager while there is an existing one");
}
@@ -535,10 +527,8 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
mWindowContextRootDisplayAreaId = rootDisplayAreaId;
mWindowContext = mDisplayContext.createWindowContext(
IMMERSIVE_MODE_CONFIRMATION_WINDOW_TYPE, options);
- WindowManager wm = mWindowContext.getSystemService(WindowManager.class);
- mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(wm, mLazyViewCapture,
- enableViewCaptureTracing());
- return mViewCaptureAwareWindowManager;
+ mWindowManager = mWindowManagerProvider.getWindowManager(mWindowContext);
+ return mWindowManager;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index ef0660fbcd1c..3d7d08910502 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -79,6 +79,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.bottomsheet.BottomSheetDialog;
@@ -148,43 +149,44 @@ public final class KeyboardShortcutListSearch {
private KeyCharacterMap mBackupKeyCharacterMap;
@VisibleForTesting
- KeyboardShortcutListSearch(Context context, WindowManager windowManager, int deviceId) {
+ KeyboardShortcutListSearch(Context context, @NonNull WindowManager windowManager,
+ int deviceId) {
this.mContext = new ContextThemeWrapper(
context, R.style.KeyboardShortcutHelper);
this.mPackageManager = AppGlobals.getPackageManager();
- if (windowManager != null) {
- this.mWindowManager = windowManager;
- } else {
- this.mWindowManager = mContext.getSystemService(WindowManager.class);
- }
+ this.mWindowManager = windowManager;
loadResources(this.mContext);
createHardcodedShortcuts(deviceId);
}
- private static KeyboardShortcutListSearch getInstance(Context context, int deviceId) {
+ private static KeyboardShortcutListSearch getInstance(Context context, int deviceId,
+ WindowManagerProvider windowManagerProvider) {
if (sInstance == null) {
- sInstance = new KeyboardShortcutListSearch(context, null, deviceId);
+ WindowManager windowManager = windowManagerProvider.getWindowManager(context);
+ sInstance = new KeyboardShortcutListSearch(context, windowManager, deviceId);
}
return sInstance;
}
- public static void show(Context context, int deviceId) {
+ public static void show(Context context, int deviceId,
+ WindowManagerProvider windowManagerProvider) {
MetricsLogger.visible(context,
MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
synchronized (sLock) {
if (sInstance != null && !sInstance.mContext.equals(context)) {
dismiss();
}
- getInstance(context, deviceId).showKeyboardShortcuts(deviceId);
+ getInstance(context, deviceId, windowManagerProvider).showKeyboardShortcuts(deviceId);
}
}
- public static void toggle(Context context, int deviceId) {
+ public static void toggle(Context context, int deviceId,
+ WindowManagerProvider windowManagerProvider) {
synchronized (sLock) {
if (isShowing()) {
dismiss();
} else {
- show(context, deviceId);
+ show(context, deviceId, windowManagerProvider);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 2157d754ce87..bd6006c8faa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -70,6 +70,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settingslib.Utils;
import com.android.systemui.res.R;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import java.util.ArrayList;
import java.util.Collections;
@@ -141,38 +142,38 @@ public final class KeyboardShortcuts {
this.mContext = new ContextThemeWrapper(
context, android.R.style.Theme_DeviceDefault_Settings);
this.mPackageManager = AppGlobals.getPackageManager();
- if (windowManager != null) {
- this.mWindowManager = windowManager;
- } else {
- this.mWindowManager = mContext.getSystemService(WindowManager.class);
- }
+ this.mWindowManager = windowManager;
loadResources(context);
}
- private static KeyboardShortcuts getInstance(Context context) {
+ private static KeyboardShortcuts getInstance(Context context,
+ WindowManagerProvider windowManagerProvider) {
if (sInstance == null) {
- sInstance = new KeyboardShortcuts(context, null);
+ WindowManager windowManager = windowManagerProvider.getWindowManager(context);
+ sInstance = new KeyboardShortcuts(context, windowManager);
}
return sInstance;
}
- public static void show(Context context, int deviceId) {
+ public static void show(Context context, int deviceId,
+ WindowManagerProvider windowManagerProvider) {
MetricsLogger.visible(context,
MetricsProto.MetricsEvent.KEYBOARD_SHORTCUTS_HELPER);
synchronized (sLock) {
if (sInstance != null && !sInstance.mContext.equals(context)) {
dismiss();
}
- getInstance(context).showKeyboardShortcuts(deviceId);
+ getInstance(context, windowManagerProvider).showKeyboardShortcuts(deviceId);
}
}
- public static void toggle(Context context, int deviceId) {
+ public static void toggle(Context context, int deviceId,
+ WindowManagerProvider windowManagerProvider) {
synchronized (sLock) {
if (isShowing()) {
dismiss();
} else {
- show(context, deviceId);
+ show(context, deviceId, windowManagerProvider);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
index 815f1fcfdec6..54c84aa139cc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutsReceiver.java
@@ -24,6 +24,7 @@ import android.content.Intent;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import javax.inject.Inject;
@@ -31,10 +32,13 @@ import javax.inject.Inject;
public class KeyboardShortcutsReceiver extends BroadcastReceiver {
private final FeatureFlags mFeatureFlags;
+ private final WindowManagerProvider mWindowManagerProvider;
@Inject
- public KeyboardShortcutsReceiver(FeatureFlags featureFlags) {
+ public KeyboardShortcutsReceiver(FeatureFlags featureFlags,
+ WindowManagerProvider windowManagerProvider) {
mFeatureFlags = featureFlags;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
@@ -44,13 +48,14 @@ public class KeyboardShortcutsReceiver extends BroadcastReceiver {
}
if (isTabletLayoutFlagEnabled() && Utilities.isLargeScreen(context)) {
if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
- KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */);
+ KeyboardShortcutListSearch.show(context, -1 /* deviceId unknown */,
+ mWindowManagerProvider);
} else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
KeyboardShortcutListSearch.dismiss();
}
} else {
if (Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
- KeyboardShortcuts.show(context, -1 /* deviceId unknown */);
+ KeyboardShortcuts.show(context, -1 /* deviceId unknown */, mWindowManagerProvider);
} else if (Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS.equals(intent.getAction())) {
KeyboardShortcuts.dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index fce5a16bd85d..e292bcf1f7a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -24,6 +24,7 @@ import android.util.IndentingPrintWriter
import android.util.Log
import android.util.MathUtils
import android.view.Choreographer
+import android.view.Display
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.dynamicanimation.animation.FloatPropertyCompat
@@ -42,7 +43,9 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.shade.ShadeExpansionChangeEvent
import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
+import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
import com.android.systemui.statusbar.phone.DozeParameters
@@ -52,6 +55,7 @@ import com.android.systemui.util.WallpaperController
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor
import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import com.android.wm.shell.appzoomout.AppZoomOut
+import dagger.Lazy
import java.io.PrintWriter
import java.util.Optional
import javax.inject.Inject
@@ -83,6 +87,7 @@ constructor(
private val appZoomOutOptional: Optional<AppZoomOut>,
@Application private val applicationScope: CoroutineScope,
dumpManager: DumpManager,
+ private val shadeDisplaysRepository: Lazy<ShadeDisplaysRepository>,
) : ShadeExpansionListener, Dumpable {
companion object {
private const val WAKE_UP_ANIMATION_ENABLED = true
@@ -228,6 +233,14 @@ constructor(
private data class WakeAndUnlockBlurData(val radius: Float, val useZoom: Boolean = true)
+ private val isShadeOnDefaultDisplay: Boolean
+ get() =
+ if (ShadeWindowGoesAround.isEnabled) {
+ shadeDisplaysRepository.get().displayId.value == Display.DEFAULT_DISPLAY
+ } else {
+ true
+ }
+
/** Blur radius of the wake and unlock animation on this frame, and whether to zoom out. */
private var wakeAndUnlockBlurData = WakeAndUnlockBlurData(0f)
set(value) {
@@ -265,9 +278,14 @@ constructor(
var blur = shadeRadius.toInt()
// If the blur comes from waking up, we don't want to zoom out the background
val zoomOut =
- if (shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom)
- blurRadiusToZoomOut(blurRadius = shadeRadius)
- else 0f
+ when {
+ // When the shade is in another display, we don't want to zoom out the background.
+ // Only the default display is supported right now.
+ !isShadeOnDefaultDisplay -> 0f
+ shadeRadius != wakeAndUnlockBlurData.radius || wakeAndUnlockBlurData.useZoom ->
+ blurRadiusToZoomOut(blurRadius = shadeRadius)
+ else -> 0f
+ }
// Make blur be 0 if it is necessary to stop blur effect.
if (scrimsVisible) {
if (!Flags.notificationShadeBlur()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
index 2d9ccb7b09b0..33ff6c014125 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/mediaprojection/ui/view/EndMediaProjectionDialogHelper.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.chips.mediaprojection.ui.view
import android.app.ActivityManager
import android.content.DialogInterface
import android.content.pm.PackageManager
+import android.util.Log
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.mediaprojection.data.model.MediaProjectionState
@@ -54,10 +55,6 @@ constructor(
// dialog to animate back into the chip just for the chip to disappear in a few frames.
dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations()
stopAction.invoke()
- // TODO(b/332662551): If the projection is stopped, there's a brief moment where the
- // dialog closes and the chip re-shows because the system APIs haven't come back and
- // told SysUI that the projection has officially stopped. It would be great for the chip
- // to not re-show at all.
}
}
@@ -85,8 +82,17 @@ constructor(
val appInfo = packageManager.getApplicationInfo(packageName, 0)
appInfo.loadLabel(packageManager)
} catch (e: PackageManager.NameNotFoundException) {
- // TODO(b/332662551): Log this error.
+ Log.w(
+ TAG,
+ "Failed to find application info for package: $packageName when creating " +
+ "end media projection dialog",
+ e,
+ )
null
}
}
+
+ companion object {
+ private const val TAG = "EndMediaProjectionDialogHelper"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
index 9380dfe32bf5..0560c9e1f774 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractor.kt
@@ -145,7 +145,9 @@ constructor(
/**
* Emits all notifications that are eligible to show as chips in the status bar. This is
- * different from which chips will *actually* show, see [shownNotificationChips] for that.
+ * different from which chips will *actually* show, because
+ * [com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel] will
+ * hide chips that have [NotificationChipModel.isAppVisible] as true.
*/
val allNotificationChips: Flow<List<NotificationChipModel>> =
if (StatusBarNotifChips.isEnabled) {
@@ -186,15 +188,6 @@ constructor(
initialValue = emptyList(),
)
- /** Emits the notifications that should actually be *shown* as chips in the status bar. */
- val shownNotificationChips: Flow<List<NotificationChipModel>> =
- allNotificationChips.map { chipsList ->
- // If the app that posted this notification is visible, we want to hide the chip
- // because information between the status bar chip and the app itself could be
- // out-of-sync (like a timer that's slightly off)
- chipsList.filter { !it.isAppVisible }
- }
-
/*
Stable sort the promoted notifications by two criteria:
Criteria #1: Whichever app was most recently visible has higher ranking.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index 1f2079d83e6f..356731cb3777 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -17,7 +17,7 @@
package com.android.systemui.statusbar.chips.notification.domain.model
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
@@ -25,7 +25,7 @@ data class NotificationChipModel(
/** The user-readable name of the app that posted this notification. */
val appName: String,
val statusBarChipIconView: StatusBarIconView?,
- val promotedContent: PromotedNotificationContentModel,
+ val promotedContent: PromotedNotificationContentModels,
/** The time when the notification first appeared as promoted. */
val creationTime: Long,
/** True if the app managing this notification is currently visible to the user. */
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 b303751b4d6e..e45524b59837 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
@@ -55,12 +55,12 @@ constructor(
private val systemClock: SystemClock,
) {
/**
- * A flow modeling the notification chips that should be shown. Emits an empty list if there are
- * no notifications that should show a status bar chip.
+ * A flow modeling the current notification chips. Emits an empty list if there are no
+ * notifications that are eligible to show a status bar chip.
*/
val chips: Flow<List<OngoingActivityChipModel.Active>> =
combine(
- notifChipsInteractor.shownNotificationChips,
+ notifChipsInteractor.allNotificationChips,
headsUpNotificationInteractor.statusBarHeadsUpState,
) { notifications, headsUpState ->
notifications.map { it.toActivityChipModel(headsUpState) }
@@ -72,6 +72,8 @@ constructor(
headsUpState: TopPinnedState
): OngoingActivityChipModel.Active {
StatusBarNotifChips.unsafeAssertInNewMode()
+ // Chips are never shown when locked, so it's safe to use the version with sensitive content
+ val chipContent = promotedContent.privateVersion
val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
@@ -96,6 +98,10 @@ constructor(
notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
}
}
+ // If the app that posted this notification is visible, we want to hide the chip
+ // because information between the status bar chip and the app itself could be
+ // out-of-sync (like a timer that's slightly off)
+ val isHidden = this.isAppVisible
val onClickListenerLegacy =
View.OnClickListener {
StatusBarChipsModernization.assertInLegacyMode()
@@ -120,24 +126,23 @@ constructor(
colors = colors,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
- if (this.promotedContent.shortCriticalText != null) {
+ if (chipContent.shortCriticalText != null) {
return OngoingActivityChipModel.Active.Text(
key = this.key,
icon = icon,
colors = colors,
- text = this.promotedContent.shortCriticalText,
+ text = chipContent.shortCriticalText,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
- if (
- Flags.promoteNotificationsAutomatically() &&
- this.promotedContent.wasPromotedAutomatically
- ) {
+ if (Flags.promoteNotificationsAutomatically() && chipContent.wasPromotedAutomatically) {
// When we're promoting notifications automatically, the `when` time set on the
// notification will likely just be set to the current time, which would cause the chip
// to always show "now". We don't want early testers to get that experience since it's
@@ -148,32 +153,35 @@ constructor(
colors = colors,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
- if (this.promotedContent.time == null) {
+ if (chipContent.time == null) {
return OngoingActivityChipModel.Active.IconOnly(
key = this.key,
icon = icon,
colors = colors,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
- when (this.promotedContent.time) {
+ when (chipContent.time) {
is PromotedNotificationContentModel.When.Time -> {
return if (
- this.promotedContent.time.currentTimeMillis >=
+ chipContent.time.currentTimeMillis >=
systemClock.currentTimeMillis() + FUTURE_TIME_THRESHOLD_MILLIS
) {
OngoingActivityChipModel.Active.ShortTimeDelta(
key = this.key,
icon = icon,
colors = colors,
- time = this.promotedContent.time.currentTimeMillis,
+ time = chipContent.time.currentTimeMillis,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
} else {
// Don't show a `when` time that's close to now or in the past because it's
@@ -190,6 +198,7 @@ constructor(
colors = colors,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
}
@@ -198,10 +207,11 @@ constructor(
key = this.key,
icon = icon,
colors = colors,
- startTimeMs = this.promotedContent.time.elapsedRealtimeMillis,
- isEventInFuture = this.promotedContent.time.isCountDown,
+ startTimeMs = chipContent.time.elapsedRealtimeMillis,
+ isEventInFuture = chipContent.time.isCountDown,
onClickListenerLegacy = onClickListenerLegacy,
clickBehavior = clickBehavior,
+ isHidden = isHidden,
)
}
}
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 b2683762cb2a..fe6065d0bb42 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
@@ -46,8 +46,9 @@ sealed interface ColorsModel {
}
/** 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.
+ // Note: When StatusBarChipsModernization is disabled, the chip's color doesn't get
+ // 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(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index e28f3684b0fa..ba60e2ce2aa6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -235,7 +235,6 @@ sealed class OngoingActivityChipModel {
override val isImportantForPrivacy: Boolean = false,
override val icon: ChipIcon,
override val colors: ColorsModel,
- // TODO(b/361346412): Enforce a max length requirement?
val text: String,
override val onClickListenerLegacy: View.OnClickListener? = null,
override val clickBehavior: ClickBehavior,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
index 7fc5e8abe904..587d80fc3848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStore.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.data.repository
import android.view.Display
import android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -48,7 +47,6 @@ constructor(
private val windowControllerFactory: PrivacyDotWindowController.Factory,
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
private val privacyDotViewControllerStore: PrivacyDotViewControllerStore,
- private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
) :
PrivacyDotWindowControllerStore,
StatusBarPerDisplayStoreImpl<PrivacyDotWindowController>(
@@ -72,8 +70,7 @@ constructor(
return windowControllerFactory.create(
displayId = displayId,
privacyDotViewController = privacyDotViewController,
- viewCaptureAwareWindowManager =
- viewCaptureAwareWindowManagerFactory.create(displayWindowProperties.windowManager),
+ windowManager = displayWindowProperties.windowManager,
inflater = displayWindowProperties.layoutInflater,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
index e2bcfb752e6c..7999ca9fd3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotWindowController.kt
@@ -24,10 +24,10 @@ import android.view.DisplayCutout.BOUNDS_POSITION_RIGHT
import android.view.DisplayCutout.BOUNDS_POSITION_TOP
import android.view.LayoutInflater
import android.view.View
+import android.view.WindowManager
import android.view.WindowManager.InvalidDisplayException
import android.view.WindowManager.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.ScreenDecorations
import com.android.systemui.ScreenDecorationsThread
import com.android.systemui.decor.DecorProvider
@@ -54,7 +54,7 @@ class PrivacyDotWindowController
constructor(
@Assisted private val displayId: Int,
@Assisted private val privacyDotViewController: PrivacyDotViewController,
- @Assisted private val viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ @Assisted private val windowManager: WindowManager,
@Assisted private val inflater: LayoutInflater,
@ScreenDecorationsThread private val uiExecutor: Executor,
private val dotFactory: PrivacyDotDecorProviderFactory,
@@ -106,7 +106,7 @@ constructor(
try {
// Wrapping this in a try/catch to avoid crashes when a display is instantly removed
// after being added, and initialization hasn't finished yet.
- viewCaptureAwareWindowManager.addView(rootView, params)
+ windowManager.addView(rootView, params)
} catch (e: InvalidDisplayException) {
Log.e(
TAG,
@@ -118,7 +118,7 @@ constructor(
}
fun stop() {
- dotViews.forEach { viewCaptureAwareWindowManager.removeView(it) }
+ dotViews.forEach { windowManager.removeView(it) }
}
@AssistedFactory
@@ -126,7 +126,7 @@ constructor(
fun create(
displayId: Int,
privacyDotViewController: PrivacyDotViewController,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
inflater: LayoutInflater,
): PrivacyDotWindowController
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4558017a98c8..b5ab0920a470 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -198,7 +199,7 @@ public final class NotificationEntry extends ListEntry {
// TODO(b/377565433): Move into NotificationContentModel during/after
// NotificationRowContentBinderRefactor.
- private PromotedNotificationContentModel mPromotedNotificationContentModel;
+ private PromotedNotificationContentModels mPromotedNotificationContentModels;
/**
* True if both
@@ -1106,9 +1107,9 @@ public final class NotificationEntry extends ListEntry {
* Gets the content needed to render this notification as a promoted notification on various
* surfaces (like status bar chips and AOD).
*/
- public PromotedNotificationContentModel getPromotedNotificationContentModel() {
+ public PromotedNotificationContentModels getPromotedNotificationContentModels() {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- return mPromotedNotificationContentModel;
+ return mPromotedNotificationContentModels;
} else {
Log.wtf(TAG, "getting promoted content without feature flag enabled", new Throwable());
return null;
@@ -1127,10 +1128,10 @@ public final class NotificationEntry extends ListEntry {
* Sets the content needed to render this notification as a promoted notification on various
* surfaces (like status bar chips and AOD).
*/
- public void setPromotedNotificationContentModel(
- @Nullable PromotedNotificationContentModel promotedNotificationContentModel) {
+ public void setPromotedNotificationContentModels(
+ @Nullable PromotedNotificationContentModels promotedNotificationContentModels) {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- this.mPromotedNotificationContentModel = promotedNotificationContentModel;
+ this.mPromotedNotificationContentModels = promotedNotificationContentModels;
} else {
Log.wtf(TAG, "setting promoted content without feature flag enabled", new Throwable());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index a0eab43f854b..26b86f9ed74d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
+import com.android.systemui.Flags.notificationSkipSilentUpdates
+
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.util.ArrayMap
@@ -465,15 +467,32 @@ constructor(
}
hunMutator.updateNotification(posted.key, pinnedStatus)
}
- } else {
+ } else { // shouldHeadsUpEver = false
if (posted.isHeadsUpEntry) {
- // We don't want this to be interrupting anymore, let's remove it
- // If the notification is pinned by the user, the only way a user can un-pin
- // it is by tapping the status bar notification chip. Since that's a clear
- // user action, we should remove the HUN immediately instead of waiting for
- // any sort of minimum timeout.
- val shouldRemoveImmediately = posted.isPinnedByUser
- hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
+ if (notificationSkipSilentUpdates()) {
+ if (posted.isPinnedByUser) {
+ // We don't want this to be interrupting anymore, let's remove it
+ // If the notification is pinned by the user, the only way a user
+ // can un-pin it by tapping the status bar notification chip. Since
+ // that's a clear user action, we should remove the HUN immediately
+ // instead of waiting for any sort of minimum timeout.
+ // TODO(b/401068530) Ensure that status bar chip HUNs are not
+ // removed for silent update
+ hunMutator.removeNotification(posted.key,
+ /* releaseImmediately= */ true)
+ } else {
+ // Do NOT remove HUN for non-user update.
+ // Let the HUN show for its remaining duration.
+ }
+ } else {
+ // We don't want this to be interrupting anymore, let's remove it
+ // If the notification is pinned by the user, the only way a user can
+ // un-pin it is by tapping the status bar notification chip. Since
+ // that's a clear user action, we should remove the HUN immediately
+ // instead of waiting for any sort of minimum timeout.
+ val shouldRemoveImmediately = posted.isPinnedByUser
+ hunMutator.removeNotification(posted.key, shouldRemoveImmediately)
+ }
} else {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
@@ -573,24 +592,34 @@ constructor(
isBinding = isBinding,
)
}
- // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter, so
- // that
- // work can be done before the ShadeListBuilder is run. This prevents re-entrant
- // behavior between this Coordinator, HeadsUpManager, and VisualStabilityManager.
- if (posted?.shouldHeadsUpEver == false) {
- if (posted.isHeadsUpEntry) {
- // We don't want this to be interrupting anymore, let's remove it
- mHeadsUpManager.removeNotification(
- posted.key,
- /* removeImmediately= */ false,
- "onEntryUpdated",
- )
- } else if (posted.isBinding) {
+ if (notificationSkipSilentUpdates()) {
+ // TODO(b/403703828) Move canceling to OnBeforeFinalizeFilter, since we are not
+ // removing from HeadsUpManager and don't need to deal with re-entrant behavior
+ // between HeadsUpCoordinator, HeadsUpManager, and VisualStabilityManager.
+ if (posted?.shouldHeadsUpEver == false
+ && !posted.isHeadsUpEntry && posted.isBinding) {
// Don't let the bind finish
cancelHeadsUpBind(posted.entry)
}
+ } else {
+ // Handle cancelling heads up here, rather than in the OnBeforeFinalizeFilter,
+ // so that work can be done before the ShadeListBuilder is run. This prevents
+ // re-entrant behavior between this Coordinator, HeadsUpManager, and
+ // VisualStabilityManager.
+ if (posted?.shouldHeadsUpEver == false) {
+ if (posted.isHeadsUpEntry) {
+ // We don't want this to be interrupting anymore, let's remove it
+ mHeadsUpManager.removeNotification(
+ posted.key,
+ /* removeImmediately= */ false,
+ "onEntryUpdated",
+ )
+ } else if (posted.isBinding) {
+ // Don't let the bind finish
+ cancelHeadsUpBind(posted.entry)
+ }
+ }
}
-
// Update last updated time for this entry
setUpdateTime(entry, mSystemClock.currentTimeMillis())
}
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 1e5aa01714be..6042bff4bb97 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
@@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.logging.dagger.NotificationsL
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractorImpl;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
-import com.android.systemui.statusbar.notification.row.NotificationActionClickManager;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactory;
import com.android.systemui.statusbar.notification.row.NotificationEntryProcessorFactoryLooperImpl;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -325,7 +324,7 @@ public interface NotificationsModule {
if (PromotedNotificationContentModel.featureFlagEnabled()) {
return implProvider.get();
} else {
- return (entry, recoveredBuilder, imageModelProvider) -> null;
+ return (entry, recoveredBuilder, redactionType, imageModelProvider) -> null;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 2f9d86b45d1f..e7cc342ab65c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -36,6 +36,7 @@ import com.android.systemui.statusbar.notification.collection.provider.SectionSt
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -141,7 +142,7 @@ private class ActiveNotificationsStoreBuilder(
private fun NotificationEntry.toModel(): ActiveNotificationModel {
val promotedContent =
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- promotedNotificationContentModel
+ promotedNotificationContentModels
} else {
null
}
@@ -199,7 +200,7 @@ private fun ActiveNotificationsStore.createOrReuse(
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
- promotedContent: PromotedNotificationContentModel?,
+ promotedContent: PromotedNotificationContentModels?,
): ActiveNotificationModel {
return individuals[key]?.takeIf {
it.isCurrent(
@@ -281,7 +282,7 @@ private fun ActiveNotificationModel.isCurrent(
isGroupSummary: Boolean,
bucket: Int,
callType: CallType,
- promotedContent: PromotedNotificationContentModel?,
+ promotedContent: PromotedNotificationContentModels?,
): Boolean {
return when {
key != this.key -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index d09546fe80ca..3caaf542e787 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -67,6 +67,7 @@ public class FooterView extends StackScrollerDecorView {
private FooterViewButton mSettingsButton;
private FooterViewButton mHistoryButton;
private boolean mShouldBeHidden;
+ private boolean mIsBlurSupported;
// Footer label
private TextView mSeenNotifsFooterTextView;
@@ -390,15 +391,20 @@ public class FooterView extends StackScrollerDecorView {
if (!notificationFooterBackgroundTintOptimization()) {
if (notificationShadeBlur()) {
- Color backgroundColor = Color.valueOf(
- SurfaceEffectColors.surfaceEffect1(getContext()));
- scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
- // Apply alpha on background drawables.
- int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
- clearAllBg.setAlpha(backgroundAlpha);
- settingsBg.setAlpha(backgroundAlpha);
- if (historyBg != null) {
- historyBg.setAlpha(backgroundAlpha);
+ if (mIsBlurSupported) {
+ Color backgroundColor = Color.valueOf(
+ SurfaceEffectColors.surfaceEffect1(getContext()));
+ scHigh = ColorUtils.setAlphaComponent(backgroundColor.toArgb(), 0xFF);
+ // Apply alpha on background drawables.
+ int backgroundAlpha = (int) (backgroundColor.alpha() * 0xFF);
+ clearAllBg.setAlpha(backgroundAlpha);
+ settingsBg.setAlpha(backgroundAlpha);
+ if (historyBg != null) {
+ historyBg.setAlpha(backgroundAlpha);
+ }
+ } else {
+ scHigh = mContext.getColor(
+ com.android.internal.R.color.materialColorSurfaceContainer);
}
} else {
scHigh = mContext.getColor(
@@ -438,6 +444,16 @@ public class FooterView extends StackScrollerDecorView {
}
}
+ public void setIsBlurSupported(boolean isBlurSupported) {
+ if (notificationShadeBlur()) {
+ if (mIsBlurSupported == isBlurSupported) {
+ return;
+ }
+ mIsBlurSupported = isBlurSupported;
+ updateColors();
+ }
+ }
+
@Override
@NonNull
public ExpandableViewState createExpandableViewState() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
index 3383ce98549f..3213754ca9a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewbinder/FooterViewBinder.kt
@@ -20,6 +20,7 @@ import android.view.View
import androidx.lifecycle.lifecycleScope
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
@@ -81,6 +82,14 @@ object FooterViewBinder {
launch { bindHistoryButton(footer, viewModel, notificationActivityStarter) }
}
launch { bindMessage(footer, viewModel) }
+
+ if (notificationShadeBlur()) {
+ launch {
+ viewModel.isBlurSupported.collect { supported ->
+ footer.setIsBlurSupported(supported)
+ }
+ }
+ }
}
private suspend fun bindClearAllButton(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index c895c41960d4..c1fc72c618ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -31,6 +31,7 @@ import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
import com.android.systemui.util.ui.AnimatedValue
import com.android.systemui.util.ui.toAnimatedValueFlow
+import com.android.systemui.window.domain.interactor.WindowRootViewBlurInteractor
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
@@ -48,6 +49,7 @@ constructor(
notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
shadeInteractor: ShadeInteractor,
+ windowRootViewBlurInteractor: WindowRootViewBlurInteractor,
) {
/** A message to show instead of the footer buttons. */
val message: FooterMessageViewModel =
@@ -119,6 +121,8 @@ constructor(
}
}
+ val isBlurSupported = windowRootViewBlurInteractor.isBlurCurrentlySupported
+
private val manageOrHistoryButtonText: Flow<Int> =
notificationSettingsInteractor.isNotificationHistoryEnabled.map { shouldLaunchHistory ->
if (shouldLaunchHistory) R.string.manage_notifications_history_text
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 27b2788f0b08..a8a7e885d1f7 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
@@ -40,6 +40,8 @@ import android.graphics.drawable.Icon
import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.shade.ShadeDisplayAware
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_AUTOMATICALLY_EXTRACTED_SHORT_CRITICAL_TEXT
import com.android.systemui.statusbar.notification.promoted.AutomaticPromotionCoordinator.Companion.EXTRA_WAS_AUTOMATICALLY_PROMOTED
@@ -48,6 +50,7 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.OldProgress
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.When
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.shared.ImageModel
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider.ImageSizeClass.MediumSquare
@@ -60,8 +63,9 @@ interface PromotedNotificationContentExtractor {
fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel?
+ ): PromotedNotificationContentModels?
}
@SysUISingleton
@@ -76,8 +80,9 @@ constructor(
override fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel? {
+ ): PromotedNotificationContentModels? {
if (!PromotedNotificationContentModel.featureFlagEnabled()) {
logger.logExtractionSkipped(entry, "feature flags disabled")
return null
@@ -95,7 +100,55 @@ constructor(
return null
}
- val contentBuilder = PromotedNotificationContentModel.Builder(entry.key)
+ val privateVersion =
+ extractPrivateContent(
+ key = entry.key,
+ notification = notification,
+ recoveredBuilder = recoveredBuilder,
+ lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs,
+ imageModelProvider = imageModelProvider,
+ )
+ val publicVersion =
+ if (redactionType == REDACTION_TYPE_NONE) {
+ privateVersion
+ } else {
+ if (notification.publicVersion == null) {
+ privateVersion.toDefaultPublicVersion()
+ } else {
+ // TODO(b/400991304): implement extraction for [Notification.publicVersion]
+ privateVersion.toDefaultPublicVersion()
+ }
+ }
+ return PromotedNotificationContentModels(
+ privateVersion = privateVersion,
+ publicVersion = publicVersion,
+ )
+ .also { logger.logExtractionSucceeded(entry, it) }
+ }
+
+ private fun PromotedNotificationContentModel.toDefaultPublicVersion():
+ PromotedNotificationContentModel =
+ PromotedNotificationContentModel.Builder(key = identity.key).let {
+ it.style = if (style == Style.Ineligible) Style.Ineligible else Style.Base
+ it.smallIcon = smallIcon
+ it.iconLevel = iconLevel
+ it.appName = appName
+ it.time = time
+ it.lastAudiblyAlertedMs = lastAudiblyAlertedMs
+ it.profileBadgeResId = profileBadgeResId
+ it.colors = colors
+ it.build()
+ }
+
+ private fun extractPrivateContent(
+ key: String,
+ notification: Notification,
+ recoveredBuilder: Notification.Builder,
+ lastAudiblyAlertedMs: Long,
+ imageModelProvider: ImageModelProvider,
+ ): PromotedNotificationContentModel {
+
+ val contentBuilder = PromotedNotificationContentModel.Builder(key)
// TODO: Pitch a fit if style is unsupported or mandatory fields are missing once
// FLAG_PROMOTED_ONGOING is set reliably and we're not testing status bar chips.
@@ -108,7 +161,7 @@ constructor(
contentBuilder.subText = notification.subText()
contentBuilder.time = notification.extractWhen()
contentBuilder.shortCriticalText = notification.shortCriticalText()
- contentBuilder.lastAudiblyAlertedMs = entry.lastAudiblyAlertedMs
+ contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
contentBuilder.title = notification.title(recoveredBuilder.style)
contentBuilder.text = notification.text(recoveredBuilder.style)
@@ -124,7 +177,7 @@ constructor(
recoveredBuilder.extractStyleContent(notification, contentBuilder, imageModelProvider)
- return contentBuilder.build().also { logger.logExtractionSucceeded(entry, it) }
+ return contentBuilder.build()
}
private fun Notification.smallIconModel(imageModelProvider: ImageModelProvider): ImageModel? =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 5f9678a06b59..6b6203d6ea4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -23,7 +23,7 @@ import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.core.LogLevel.INFO
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import javax.inject.Inject
@OptIn(ExperimentalStdlibApi::class)
@@ -56,7 +56,7 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
fun logExtractionSucceeded(
entry: NotificationEntry,
- content: PromotedNotificationContentModel,
+ content: PromotedNotificationContentModels,
) {
buffer.log(
EXTRACTION_TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index ec4ee4560ea1..d9778bdde0a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -22,6 +22,7 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@SysUISingleton
@@ -34,6 +35,16 @@ constructor(
/** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
promotedNotificationsInteractor.aodPromotedNotification
+ .map {
+ // TODO(b/400991304): show the private version when unlocked
+ it?.publicVersion
+ }
+ .distinctUntilNewInstance()
val isPresent: Flow<Boolean> = content.map { it != null }.dumpWhileCollecting("isPresent")
+
+ /**
+ * Returns flow where all subsequent repetitions of the same object instance are filtered out.
+ */
+ private fun <T> Flow<T>.distinctUntilNewInstance() = distinctUntilChanged { a, b -> a === b }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
index 96d41f1b3aaf..08e7528631c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/PromotedNotificationsInteractor.kt
@@ -25,8 +25,8 @@ import com.android.systemui.statusbar.chips.notification.domain.interactor.Statu
import com.android.systemui.statusbar.chips.screenrecord.domain.interactor.ScreenRecordChipInteractor
import com.android.systemui.statusbar.chips.screenrecord.domain.model.ScreenRecordChipModel
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel.Style.Ineligible
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import javax.inject.Inject
@@ -201,13 +201,13 @@ constructor(
* The top promoted notification represented by a chip, with the order determined by the order
* of the chips, not the notifications.
*/
- private val topPromotedChipNotification: Flow<PromotedNotificationContentModel?> =
+ private val topPromotedChipNotification: Flow<PromotedNotificationContentModels?> =
orderedChipNotifications
.map { list -> list.firstNotNullOfOrNull { it.promotedContent } }
.distinctUntilNewInstance()
/** This is the AOD promoted notification, which should avoid regular changing. */
- val aodPromotedNotification: Flow<PromotedNotificationContentModel?> =
+ val aodPromotedNotification: Flow<PromotedNotificationContentModels?> =
combine(
topPromotedChipNotification,
activeNotificationsInteractor.topLevelRepresentativeNotifications,
@@ -229,13 +229,13 @@ constructor(
.flowOn(backgroundDispatcher)
private fun List<ActiveNotificationModel>.firstAodEligibleOrNull():
- PromotedNotificationContentModel? {
+ PromotedNotificationContentModels? {
return this.firstNotNullOfOrNull { it.promotedContent?.takeIfAodEligible() }
}
- private fun PromotedNotificationContentModel.takeIfAodEligible():
- PromotedNotificationContentModel? {
- return this.takeUnless { it.style == Ineligible }
+ private fun PromotedNotificationContentModels.takeIfAodEligible():
+ PromotedNotificationContentModels? {
+ return this.takeUnless { it.privateVersion.style == Ineligible }
}
/**
@@ -251,7 +251,7 @@ constructor(
*/
private data class NotifAndPromotedContent(
val key: String,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) {
/**
* Define the equals of this object to only check the reference equality of the promoted
@@ -269,7 +269,7 @@ constructor(
/** Define the hashCode to be very quick, even if it increases collisions. */
override fun hashCode(): Int {
var result = key.hashCode()
- result = 31 * result + (promotedContent?.identity?.hashCode() ?: 0)
+ result = 31 * result + (promotedContent?.key?.hashCode() ?: 0)
return result
}
}
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 57b07204fc6a..ffacf62fccce 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
@@ -29,6 +29,31 @@ import com.android.systemui.statusbar.notification.row.ImageResult
import com.android.systemui.statusbar.notification.row.LazyImage
import com.android.systemui.statusbar.notification.row.shared.ImageModel
+data class PromotedNotificationContentModels(
+ /** The potentially redacted version of the content that will be exposed to the public */
+ val publicVersion: PromotedNotificationContentModel,
+ /** The unredacted version of the content that will be kept private */
+ val privateVersion: PromotedNotificationContentModel,
+) {
+ val key: String
+ get() = privateVersion.identity.key
+
+ init {
+ check(publicVersion.identity.key == privateVersion.identity.key) {
+ "public and private models must have the same key"
+ }
+ }
+
+ fun toRedactedString(): String {
+ val publicVersionString =
+ "==privateVersion".takeIf { privateVersion === publicVersion }
+ ?: publicVersion.toRedactedString()
+ return ("PromotedNotificationContentModels(" +
+ "privateVersion=${privateVersion.toRedactedString()}, " +
+ "publicVersion=$publicVersionString)")
+ }
+}
+
/**
* The content needed to render a promoted notification to surfaces besides the notification stack,
* like the skeleton view on AOD or the status bar chip.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index dfadf74c71b1..bef3c691cb4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2686,30 +2686,58 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * Whether to allow dismissal with the whole-row translation animation.
+ *
+ * If true, either animation is permissible.
+ * If false, usingRTX behavior is forbidden, only clipping animation should be used.
+ *
+ * Usually either is OK, except for promoted notifications, where we always need to
+ * dismiss with content clipping/partial translation animation instead, so that we
+ * can show the demotion options.
+ * @return
+ */
+ private boolean allowDismissUsingRowTranslationX() {
+ if (Flags.permissionHelperInlineUiRichOngoing()) {
+ return !isPromotedOngoing();
+ } else {
+ // Don't change behavior unless the flag is on.
+ return true;
+ }
+ }
+
+ /**
* Set the dismiss behavior of the view.
*
* @param usingRowTranslationX {@code true} if the view should translate using regular
* translationX, otherwise the contents will be
* translated.
+ * @param forceUpdateChildren {@code true} to force initialization, {@code false} if lazy
+ * behavior is OK.
*/
@Override
- public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
- if (usingRowTranslationX != mDismissUsingRowTranslationX) {
+ public void setDismissUsingRowTranslationX(boolean usingRowTranslationX,
+ boolean forceUpdateChildren) {
+ // Before updating dismiss behavior, make sure this is an allowable configuration for this
+ // notification.
+ usingRowTranslationX = usingRowTranslationX && allowDismissUsingRowTranslationX();
+
+ if (forceUpdateChildren || (usingRowTranslationX != mDismissUsingRowTranslationX)) {
// In case we were already transitioning, let's switch over!
float previousTranslation = getTranslation();
if (previousTranslation != 0) {
setTranslation(0);
}
- super.setDismissUsingRowTranslationX(usingRowTranslationX);
+ super.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren);
if (previousTranslation != 0) {
setTranslation(previousTranslation);
}
+
if (mChildrenContainer != null) {
List<ExpandableNotificationRow> notificationChildren =
mChildrenContainer.getAttachedChildren();
for (int i = 0; i < notificationChildren.size(); i++) {
ExpandableNotificationRow child = notificationChildren.get(i);
- child.setDismissUsingRowTranslationX(usingRowTranslationX);
+ child.setDismissUsingRowTranslationX(usingRowTranslationX, forceUpdateChildren);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 80cf818e985f..6c990df5d05e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -292,7 +292,8 @@ public abstract class ExpandableOutlineView extends ExpandableView {
* translationX, otherwise the contents will be
* translated.
*/
- public void setDismissUsingRowTranslationX(boolean usingRowTranslationX) {
+ public void setDismissUsingRowTranslationX(boolean usingRowTranslationX,
+ boolean forceUpdateChildren) {
mDismissUsingRowTranslationX = usingRowTranslationX;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index ba80f016cad4..4c7c46dfae94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -51,7 +51,7 @@ import java.util.Objects;
*/
public class HybridConversationNotificationView extends HybridNotificationView {
- private static final int MAX_SUMMARIZATION_LINES = 2;
+ private static final int MAX_SUMMARIZATION_LINES = 1;
private ImageView mConversationIconView;
private TextView mConversationSenderName;
private ViewStub mConversationFacePileStub;
@@ -295,7 +295,6 @@ public class HybridConversationNotificationView extends HybridNotificationView {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return;
if (!TextUtils.isEmpty(summarization)) {
mConversationSenderName.setVisibility(GONE);
- titleText = null;
contentText = summarization;
mTextView.setSingleLine(false);
mTextView.setMaxLines(MAX_SUMMARIZATION_LINES);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index d97e25fdfa22..57ceafcd15c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -60,6 +60,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor;
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel;
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.AsyncHybridViewInflation;
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider;
@@ -1003,7 +1004,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
row.mImageModelIndex = result.mRowImageInflater.getNewImageIndex();
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.setPromotedNotificationContentModel(result.mPromotedContent);
+ entry.setPromotedNotificationContentModels(result.mPromotedContent);
}
boolean setRepliesAndActions = true;
@@ -1387,9 +1388,9 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mLogger.logAsyncTaskProgress(logKey, "extracting promoted notification content");
final ImageModelProvider imageModelProvider =
result.mRowImageInflater.useForContentModel();
- final PromotedNotificationContentModel promotedContent =
+ final PromotedNotificationContentModels promotedContent =
mPromotedNotificationContentExtractor.extractContent(mEntry,
- recoveredBuilder, imageModelProvider);
+ recoveredBuilder, mBindParams.redactionType, imageModelProvider);
mLogger.logAsyncTaskProgress(logKey, "extracted promoted notification content: "
+ promotedContent);
@@ -1503,7 +1504,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
static class InflationProgress {
RowImageInflater mRowImageInflater;
- PromotedNotificationContentModel mPromotedContent;
+ PromotedNotificationContentModels mPromotedContent;
private RemoteViews newContentView;
private RemoteViews newHeadsUpView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index cdb78d99538b..f4e01bf718d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -29,6 +29,7 @@ import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
@@ -309,6 +310,7 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
});
View gutsView = item.getGutsView();
+
try {
if (gutsView instanceof NotificationSnooze) {
initializeSnoozeView(row, (NotificationSnooze) gutsView);
@@ -322,6 +324,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
(PartialConversationInfo) gutsView);
} else if (gutsView instanceof FeedbackInfo) {
initializeFeedbackInfo(row, (FeedbackInfo) gutsView);
+ } else if (gutsView instanceof PromotedPermissionGutsContent) {
+ initializeDemoteView(row, (PromotedPermissionGutsContent) gutsView);
}
return true;
} catch (Exception e) {
@@ -351,6 +355,31 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
}
/**
+ * Sets up the {@link NotificationSnooze} inside the notification row's guts.
+ *
+ * @param row view to set up the guts for
+ * @param demoteGuts view to set up/bind within {@code row}
+ */
+ private void initializeDemoteView(
+ final ExpandableNotificationRow row,
+ PromotedPermissionGutsContent demoteGuts) {
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ demoteGuts.setStatusBarNotification(sbn);
+ demoteGuts.setOnDemoteAction(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ // TODO(b/391661009): Signal AutomaticPromotionCoordinator here
+ mNotificationManager.setCanBePromoted(
+ sbn.getPackageName(), sbn.getUid(), false, true);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Couldn't revoke live update permission", e);
+ }
+ }
+ });
+ }
+
+ /**
* Sets up the {@link FeedbackInfo} inside the notification row's guts.
*
* @param row view to set up the guts for
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index c03dc279888f..f494a4ce40dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -272,6 +272,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
} else if (personNotifType >= PeopleNotificationIdentifier.TYPE_FULL_PERSON) {
mInfoItem = createConversationItem(mContext);
} else if (android.app.Flags.uiRichOngoing()
+ && android.app.Flags.apiRichOngoing()
&& Flags.permissionHelperUiRichOngoing()
&& sbn.getNotification().isPromotedOngoing()) {
mInfoItem = createPromotedItem(mContext);
@@ -284,6 +285,15 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
mRightMenuItems.add(mInfoItem);
mRightMenuItems.add(mFeedbackItem);
+ boolean isPromotedOngoing = NotificationBundleUi.isEnabled()
+ ? mParent.getEntryAdapter().isPromotedOngoing()
+ : mParent.getEntryLegacy().isPromotedOngoing();
+ if (android.app.Flags.uiRichOngoing() && Flags.permissionHelperInlineUiRichOngoing()
+ && isPromotedOngoing) {
+ mRightMenuItems.add(createDemoteItem(mContext));
+ }
+
+
mLeftMenuItems.addAll(mRightMenuItems);
populateMenuViews();
@@ -305,15 +315,19 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
} else {
mMenuContainer = new FrameLayout(mContext);
}
+
final int showDismissSetting = Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.SHOW_NEW_NOTIF_DISMISS, /* default = */ 1);
final boolean newFlowHideShelf = showDismissSetting == 1;
- if (newFlowHideShelf) {
- return;
- }
- List<MenuItem> menuItems = mOnLeft ? mLeftMenuItems : mRightMenuItems;
- for (int i = 0; i < menuItems.size(); i++) {
- addMenuView(menuItems.get(i), mMenuContainer);
+
+ // Populate menu items if we are using the new permission helper (U+) or if we are using
+ // the very old dismiss setting (SC-).
+ // TODO: SHOW_NEW_NOTIF_DISMISS==0 case can likely be removed.
+ if (Flags.permissionHelperInlineUiRichOngoing() || !newFlowHideShelf) {
+ List<MenuItem> menuItems = mOnLeft ? mLeftMenuItems : mRightMenuItems;
+ for (int i = 0; i < menuItems.size(); i++) {
+ addMenuView(menuItems.get(i), mMenuContainer);
+ }
}
}
@@ -679,6 +693,15 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
return snooze;
}
+ static MenuItem createDemoteItem(Context context) {
+ PromotedPermissionGutsContent demoteContent =
+ (PromotedPermissionGutsContent) LayoutInflater.from(context).inflate(
+ R.layout.promoted_permission_guts, null, false);
+ MenuItem info = new NotificationMenuItem(context, null, demoteContent,
+ R.drawable.unpin_icon);
+ return info;
+ }
+
static NotificationMenuItem createConversationItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -686,7 +709,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
(NotificationConversationInfo) LayoutInflater.from(context).inflate(
R.layout.notification_conversation_info, null, false);
return new NotificationMenuItem(context, infoDescription, infoContent,
- R.drawable.ic_settings);
+ NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
}
static NotificationMenuItem createPromotedItem(Context context) {
@@ -696,7 +719,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
(PromotedNotificationInfo) LayoutInflater.from(context).inflate(
R.layout.promoted_notification_info, null, false);
return new NotificationMenuItem(context, infoDescription, infoContent,
- R.drawable.ic_settings);
+ NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
}
static NotificationMenuItem createPartialConversationItem(Context context) {
@@ -706,7 +729,7 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
(PartialConversationInfo) LayoutInflater.from(context).inflate(
R.layout.partial_conversation_info, null, false);
return new NotificationMenuItem(context, infoDescription, infoContent,
- R.drawable.ic_settings);
+ NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
}
static NotificationMenuItem createInfoItem(Context context) {
@@ -718,14 +741,14 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
layoutId, null, false);
return new NotificationMenuItem(context, infoDescription, infoContent,
- R.drawable.ic_settings);
+ NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
}
static MenuItem createFeedbackItem(Context context) {
FeedbackInfo feedbackContent = (FeedbackInfo) LayoutInflater.from(context).inflate(
R.layout.feedback_info, null, false);
MenuItem info = new NotificationMenuItem(context, null, feedbackContent,
- -1 /*don't show in slow swipe menu */);
+ NotificationMenuItem.OMIT_FROM_SWIPE_MENU);
return info;
}
@@ -762,6 +785,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
@Override
public boolean isWithinSnapMenuThreshold() {
+ if (getSpaceForMenu() == 0) {
+ // don't snap open if there are no items
+ return false;
+ }
float translation = getTranslation();
float snapBackThreshold = getSnapBackThreshold();
float targetRight = getDismissThreshold();
@@ -803,6 +830,10 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
}
public static class NotificationMenuItem implements MenuItem {
+
+ // Constant signaling that this MenuItem should not appear in slow swipe.
+ public static final int OMIT_FROM_SWIPE_MENU = -1;
+
View mMenuView;
GutsContent mGutsContent;
String mContentDescription;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index ae52db88358a..4f1b90544403 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -53,6 +53,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationContentExtractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED
import com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP
@@ -595,7 +596,7 @@ constructor(
val rowImageInflater: RowImageInflater,
val remoteViews: NewRemoteViews,
val contentModel: NotificationContentModel,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) {
var inflatedContentView: View? = null
@@ -700,7 +701,12 @@ constructor(
)
val imageModelProvider = rowImageInflater.useForContentModel()
promotedNotificationContentExtractor
- .extractContent(entry, builder, imageModelProvider)
+ .extractContent(
+ entry,
+ builder,
+ bindParams.redactionType,
+ imageModelProvider,
+ )
.also {
logger.logAsyncTaskProgress(
entry.logKey,
@@ -1519,7 +1525,7 @@ constructor(
entry.setContentModel(result.contentModel)
if (PromotedNotificationContentModel.featureFlagEnabled()) {
- entry.promotedNotificationContentModel = result.promotedContent
+ entry.promotedNotificationContentModels = result.promotedContent
}
result.inflatedSmartReplyState?.let { row.privateLayout.setInflatedSmartReplyState(it) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
index 01ee788f7fd7..769f0b5a4fa4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedNotificationInfo.java
@@ -80,6 +80,7 @@ public class PromotedNotificationInfo extends NotificationInfo {
assistantFeedbackController, metricsLogger, onCloseClick);
mNotificationManager = iNotificationManager;
+
mPackageDemotionInteractor = packageDemotionInteractor;
bindDemote(entry.getSbn(), pkg);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java
new file mode 100644
index 000000000000..222a1f4d8adf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/PromotedPermissionGutsContent.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.service.notification.StatusBarNotification;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.res.R;
+
+/**
+ * This GutsContent shows an explanatory interstitial telling the user they've just revoked this
+ * app's permission to post Promoted/Live notifications.
+ * If the guts are dismissed without further action, the revocation is committed.
+ * If the user hits undo, the permission is not revoked.
+ */
+public class PromotedPermissionGutsContent extends LinearLayout
+ implements NotificationGuts.GutsContent, View.OnClickListener {
+
+ private static final String TAG = "SnoozyPromotedGuts";
+
+ private NotificationGuts mGutsContainer;
+ private StatusBarNotification mSbn;
+
+ private TextView mUndoButton;
+
+ private MetricsLogger mMetricsLogger = new MetricsLogger();
+ private OnClickListener mDemoteAction;
+
+ public PromotedPermissionGutsContent(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mUndoButton = (TextView) findViewById(R.id.undo);
+ mUndoButton.setOnClickListener(this);
+ mUndoButton.setContentDescription(
+ getContext().getString(R.string.snooze_undo_content_description));
+
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ dispatchConfigurationChanged(getResources().getConfiguration());
+ }
+
+ /**
+ * Update the content description of the snooze view based on the snooze option and whether the
+ * snooze options are expanded or not.
+ * For example, this will be something like "Collapsed\u2029Snooze for 1 hour". The paragraph
+ * separator is added to introduce a break in speech, to match what TalkBack does by default
+ * when you e.g. press on a notification.
+ */
+ private void updateContentDescription() {
+ //
+ }
+
+
+ @Override
+ public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
+ if (super.performAccessibilityActionInternal(action, arguments)) {
+ return true;
+ }
+ if (action == R.id.action_snooze_undo) {
+ undoDemote(mUndoButton);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * TODO docs
+ * @param sbn
+ */
+ public void setStatusBarNotification(StatusBarNotification sbn) {
+ mSbn = sbn;
+ TextView demoteExplanation = (TextView) findViewById(R.id.demote_explain);
+ demoteExplanation.setText(mContext.getResources().getString(R.string.demote_explain_text,
+ mSbn.getPackageName()));
+ }
+
+ @Override
+ public void onClick(View v) {
+ if (mGutsContainer != null) {
+ mGutsContainer.resetFalsingCheck();
+ }
+ final int id = v.getId();
+ if (id == R.id.undo) {
+ undoDemote(v);
+ }
+
+ }
+
+ private void undoDemote(View v) {
+ // Don't commit the demote action, instead log the undo and dismiss the view.
+ mGutsContainer.closeControls(v, /* save= */ false);
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (!save) {
+ // Undo changes and let the guts handle closing the view
+ return false;
+ } else {
+ // Commit demote action.
+ mDemoteAction.onClick(this);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isLeavebehind() {
+ return true;
+ }
+
+ @Override
+ public boolean shouldBeSavedOnClose() {
+ return true;
+ }
+
+ @Override
+ public boolean needsFalsingProtection() {
+ return false;
+ }
+
+ public void setOnDemoteAction(OnClickListener demoteAction) {
+ mDemoteAction = demoteAction;
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index 487cbceb13a4..96527389b5fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -20,6 +20,7 @@ import android.graphics.drawable.Icon
import android.util.Log
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.stack.PriorityBucket
/**
@@ -88,7 +89,7 @@ data class ActiveNotificationModel(
* The content needed to render this as a promoted notification on various surfaces, or null if
* this notification cannot be rendered as a promoted notification.
*/
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
) : ActiveNotificationEntryModel() {
init {
if (!PromotedNotificationContentModel.featureFlagEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 9fea75048e3e..503256accff0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3220,8 +3220,7 @@ public class NotificationStackScrollLayout
updateAnimationState(child);
updateChronometerForChild(child);
if (child instanceof ExpandableNotificationRow row) {
- row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
-
+ row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX, /* force= */ true);
}
}
@@ -6157,7 +6156,7 @@ public class NotificationStackScrollLayout
View child = getChildAt(i);
if (child instanceof ExpandableNotificationRow) {
((ExpandableNotificationRow) child).setDismissUsingRowTranslationX(
- dismissUsingRowTranslationX);
+ dismissUsingRowTranslationX, /* force= */ false);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 000b3f643e9a..12b48eba7a96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -89,6 +89,17 @@ constructor(
source = shadeModeInteractor.shadeMode.map { getQuickSettingsShadeContentKey(it) },
)
+ /**
+ * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
+ * consumed part of the gesture.
+ */
+ val isCurrentGestureOverscroll: Boolean by
+ hydrator.hydratedStateOf(
+ traceName = "isCurrentGestureOverscroll",
+ initialValue = false,
+ source = interactor.isCurrentGestureOverscroll
+ )
+
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -157,13 +168,6 @@ constructor(
val syntheticScroll: Flow<Float> =
interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll")
- /**
- * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
- * consumed part of the gesture.
- */
- val isCurrentGestureOverscroll: Flow<Boolean> =
- interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
-
/** Whether remote input is currently active for any notification. */
val isRemoteInputActive = remoteInputInteractor.isRemoteInputActive
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index e4e56c5de65b..8a5b22183563 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -527,7 +527,8 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
break;
case MODE_SHOW_BOUNCER:
Trace.beginSection("MODE_SHOW_BOUNCER");
- mKeyguardViewController.showPrimaryBouncer(true);
+ mKeyguardViewController.showPrimaryBouncer(true,
+ "BiometricUnlockController#MODE_SHOW_BOUNCER");
Trace.endSection();
break;
case MODE_WAKE_AND_UNLOCK_FROM_DREAM:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 9d9f01b571a7..fe367a3927ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -90,7 +90,6 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.MetricsLogger;
@@ -233,6 +232,7 @@ import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -597,7 +597,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private final EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
- private final ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private final QuickAccessWalletController mWalletController;
/**
@@ -713,8 +712,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
BrightnessMirrorShowingRepository brightnessMirrorShowingRepository,
GlanceableHubContainerController glanceableHubContainerController,
EmergencyGestureIntentFactory emergencyGestureIntentFactory,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
- QuickAccessWalletController walletController
+ QuickAccessWalletController walletController,
+ WindowManager windowManager,
+ WindowManagerProvider windowManagerProvider
) {
mContext = context;
mNotificationsController = notificationsController;
@@ -852,7 +852,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mLightRevealScrim = lightRevealScrim;
- mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
+ mWindowManagerProvider = windowManagerProvider;
}
private void initBubbles(Bubbles bubbles) {
@@ -880,8 +881,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
- mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-
mDisplay = mContext.getDisplay();
mDisplayId = mDisplay.getDisplayId();
updateDisplaySize();
@@ -1716,7 +1715,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationShadeWindowController.setRequestTopUi(false, TAG);
}
}, /* isDozing= */ false, RippleShape.CIRCLE,
- sUiEventLogger, mViewCaptureAwareWindowManager).show(animationDelay);
+ sUiEventLogger, mWindowManager, mWindowManagerProvider).show(animationDelay);
}
@Override
@@ -2407,11 +2406,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
if (needsBouncer) {
- Log.d(TAG, "showBouncerOrLockScreenIfKeyguard, showingBouncer");
+ var reason = "CentralSurfacesImpl#showBouncerOrLockScreenIfKeyguard";
if (SceneContainerFlag.isEnabled()) {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */,
+ reason);
} else {
- mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+ mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */, reason);
}
}
}
@@ -2875,6 +2875,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
protected WindowManager mWindowManager;
protected IWindowManager mWindowManagerService;
private final IDreamManager mDreamManager;
+ private final WindowManagerProvider mWindowManagerProvider;
protected Display mDisplay;
private int mDisplayId;
@@ -2911,9 +2912,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
protected void toggleKeyboardShortcuts(int deviceId) {
if (shouldUseTabletKeyboardShortcuts()) {
- KeyboardShortcutListSearch.toggle(mContext, deviceId);
+ KeyboardShortcutListSearch.toggle(mContext, deviceId, mWindowManagerProvider);
} else {
- KeyboardShortcuts.toggle(mContext, deviceId);
+ KeyboardShortcuts.toggle(mContext, deviceId, mWindowManagerProvider);
}
}
@@ -2927,7 +2928,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private boolean shouldUseTabletKeyboardShortcuts() {
return mFeatureFlags.isEnabled(SHORTCUT_LIST_SEARCH_LAYOUT)
- && Utilities.isLargeScreen(mContext);
+ && Utilities.isLargeScreen(mWindowManager, mContext.getResources());
}
private void clearNotificationEffects() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index caf8a43b2aaf..67a7eee2977a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -104,6 +104,7 @@ private constructor(
// intercepted. See [View.OnTouchEvent]
if (event.source == InputDevice.SOURCE_MOUSE) {
if (event.action == MotionEvent.ACTION_UP) {
+ dispatchEventToShadeDisplayPolicy(event)
v.performClick()
shadeController.animateExpandShade()
}
@@ -113,6 +114,15 @@ private constructor(
}
}
+ private fun dispatchEventToShadeDisplayPolicy(event: MotionEvent) {
+ if (ShadeWindowGoesAround.isEnabled) {
+ // Notify the shade display policy that the status bar was touched. This may cause
+ // the shade to change display if the touch was in a display different than the shade
+ // one.
+ lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width)
+ }
+ }
+
private val configurationListener =
object : ConfigurationController.ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
@@ -232,9 +242,6 @@ private constructor(
!upOrCancel || shadeController.isExpandedVisible,
)
}
- if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
- lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width)
- }
}
private fun addDarkReceivers() {
@@ -249,6 +256,9 @@ private constructor(
inner class PhoneStatusBarViewTouchHandler : Gefingerpoken {
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ dispatchEventToShadeDisplayPolicy(event)
+ }
return if (Flags.statusBarSwipeOverChip()) {
shadeViewController.handleExternalInterceptTouch(event)
} else {
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 f3d72027238f..d68f7df79cd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -538,10 +538,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
private void handleBlurSupportedChanged(boolean isBlurSupported) {
this.mIsBlurSupported = isBlurSupported;
if (Flags.bouncerUiRevamp()) {
- // TODO: animate blur fallback when the bouncer is pulled up.
- for (ScrimState state : ScrimState.values()) {
- state.setDefaultScrimAlpha(getDefaultScrimAlpha(true));
- }
+ updateDefaultScrimAlphas();
if (isBlurSupported) {
ScrimState.BOUNCER_SCRIMMED.setNotifBlurRadius(mBlurConfig.getMaxBlurRadiusPx());
} else {
@@ -549,17 +546,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
}
}
if (Flags.notificationShadeBlur()) {
- float inFrontAlpha = mInFrontAlpha;
- float behindAlpha = mBehindAlpha;
- float notifAlpha = mNotificationsAlpha;
-
mState.prepare(mState);
- applyState();
- startScrimAnimation(mScrimBehind, behindAlpha);
- startScrimAnimation(mNotificationsScrim, notifAlpha);
- startScrimAnimation(mScrimInFront, inFrontAlpha);
- dispatchBackScrimState(mScrimBehind.getViewAlpha());
- } else if (Flags.bouncerUiRevamp()) {
applyAndDispatchState();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 8c44fe56d269..512340913de2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -669,7 +669,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* show if any subsequent events are to be handled.
*/
if (!SceneContainerFlag.isEnabled() && beginShowingBouncer(event)) {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */false);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */false,
+ TAG + "#onPanelExpansionChanged");
}
if (!primaryBouncerIsOrWillBeShowing()) {
@@ -714,7 +715,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Shows the notification keyguard or the bouncer depending on
* {@link #needsFullscreenBouncer()}.
*/
- protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset) {
+ protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing, boolean isFalsingReset,
+ String reason) {
boolean showBouncer = needsFullscreenBouncer() && !mDozing;
if (Flags.simPinRaceConditionOnRestart()) {
showBouncer = showBouncer && !mIsSleeping;
@@ -726,11 +728,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mCentralSurfaces.hideKeyguard();
mSceneInteractorLazy.get().showOverlay(
Overlays.Bouncer,
- "StatusBarKeyguardViewManager.showBouncerOrKeyguard"
+ TAG + "#showBouncerOrKeyguard"
);
} else {
if (Flags.simPinRaceConditionOnRestart()) {
- if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true)) {
+ if (mPrimaryBouncerInteractor.show(/* isScrimmed= */ true, reason)) {
mAttemptsToShowBouncer = 0;
mCentralSurfaces.hideKeyguard();
} else {
@@ -744,19 +746,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
+ mAttemptsToShowBouncer++);
mExecutor.executeDelayed(() ->
showBouncerOrKeyguard(hideBouncerWhenShowing,
- isFalsingReset),
+ isFalsingReset, reason),
500);
}
}
} else {
mCentralSurfaces.hideKeyguard();
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true, reason);
}
}
} else if (!isFalsingReset) {
// Falsing resets can cause this to flicker, so don't reset in this case
Log.i(TAG, "Sim bouncer is already showing, issuing a refresh");
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true, reason);
}
} else {
@@ -776,7 +778,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* false when the user will be dragging it and translation should be deferred
* {@see KeyguardBouncer#show(boolean, boolean)}
*/
- public void showBouncer(boolean scrimmed) {
+ public void showBouncer(boolean scrimmed, String reason) {
if (SceneContainerFlag.isEnabled()) {
mDeviceEntryInteractorLazy.get().attemptDeviceEntry();
return;
@@ -787,7 +789,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mAlternateBouncerInteractor.forceShow();
updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState());
} else {
- showPrimaryBouncer(scrimmed);
+ showPrimaryBouncer(scrimmed, reason);
}
}
@@ -810,8 +812,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
*
* @param scrimmed true when the bouncer should show scrimmed, false when the user will be
* dragging it and translation should be deferred {@see KeyguardBouncer#show(boolean, boolean)}
+ * @param reason string description for what is causing the bouncer to be requested
*/
- public void showPrimaryBouncer(boolean scrimmed) {
+ @Override
+ public void showPrimaryBouncer(boolean scrimmed, String reason) {
hideAlternateBouncer(
/* updateScrim= */ false,
// When the scene framework is on, don't ever clear the pending dismiss action from
@@ -823,7 +827,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
"primary bouncer requested"
);
} else {
- mPrimaryBouncerInteractor.show(scrimmed);
+ mPrimaryBouncerInteractor.show(scrimmed, reason);
}
}
updateStates();
@@ -870,7 +874,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
);
}
- showBouncer(true);
+ showBouncer(true, TAG + "#dismissWithAction");
Trace.endSection();
return;
}
@@ -919,10 +923,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (SceneContainerFlag.isEnabled()) {
mSceneInteractorLazy.get().showOverlay(
Overlays.Bouncer,
- "StatusBarKeyguardViewManager.dismissWithAction"
+ TAG + "#dismissWithAction"
);
} else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true,
+ TAG + "#dismissWithAction, afterKeyguardGone");
}
} else {
// after authentication success, run dismiss action with the option to defer
@@ -932,10 +937,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (SceneContainerFlag.isEnabled()) {
mSceneInteractorLazy.get().showOverlay(
Overlays.Bouncer,
- "StatusBarKeyguardViewManager.dismissWithAction"
+ TAG + "#dismissWithAction"
);
} else {
- mPrimaryBouncerInteractor.show(/* isScrimmed= */ true);
+ mPrimaryBouncerInteractor.show(/* isScrimmed= */ true,
+ TAG + "#dismissWithAction");
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
@@ -992,7 +998,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
} else {
- showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset);
+ showBouncerOrKeyguard(hideBouncerWhenShowing, isFalsingReset, "reset");
}
if (!SceneContainerFlag.isEnabled() && hideBouncerWhenShowing && isBouncerShowing()) {
hideAlternateBouncer(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
index 8389aab4aac8..85fc9d4589c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarRemoteInputCallback.java
@@ -156,7 +156,8 @@ public class StatusBarRemoteInputCallback implements Callback, Callbacks,
if (!row.isPinned()) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(true);
}
- mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */);
+ mStatusBarKeyguardViewManager.showBouncer(true /* scrimmed */,
+ "StatusBarRemoteInputCallback#onLockedRemoteInput");
mPendingRemoteInputView = clicked;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index 61b7d80a8c85..b7eada1c6804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -38,7 +38,7 @@ import com.android.systemui.statusbar.chips.ui.view.ChipChronometer
import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.gesture.SwipeStatusBarAwayGestureHandler
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.OngoingCallRepository
@@ -347,7 +347,7 @@ constructor(
* If the call notification also meets promoted notification criteria, this field is filled
* in with the content related to promotion. Otherwise null.
*/
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
/** True if the call is currently ongoing (as opposed to incoming, screening, etc.). */
val isOngoing: Boolean,
/** True if the user has swiped away the status bar while in this phone call. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 322dfff8f144..9546d374b595 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -18,7 +18,7 @@ package com.android.systemui.statusbar.phone.ongoingcall.shared.model
import android.app.PendingIntent
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
/** Represents the state of any ongoing calls. */
sealed interface OngoingCallModel {
@@ -47,7 +47,7 @@ sealed interface OngoingCallModel {
val intent: PendingIntent?,
val notificationKey: String,
val appName: String,
- val promotedContent: PromotedNotificationContentModel?,
+ val promotedContent: PromotedNotificationContentModels?,
val isAppVisible: Boolean,
) : OngoingCallModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
index 4c6374b75f82..efab21fd9364 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseUserSwitcherAdapter.kt
@@ -37,7 +37,7 @@ protected constructor(
protected open val users: List<UserRecord>
get() = controller.users.filter {
(!controller.isKeyguardShowing || !it.isRestricted) &&
- (controller.isUserSwitcherEnabled || it.isCurrent)
+ (controller.isUserSwitcherEnabled || it.isCurrent || it.isSignOut)
}
init {
@@ -109,6 +109,7 @@ protected constructor(
item.isAddUser,
item.isGuest,
item.isAddSupervisedUser,
+ item.isSignOut,
isTablet,
item.isManageUsers,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index fa022b4768fc..0f8d534df659 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -28,7 +28,6 @@ import android.util.IndentingPrintWriter;
import androidx.annotation.NonNull;
import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager;
-import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
@@ -49,7 +48,7 @@ public final class DeviceStateRotationLockSettingController
private final RotationPolicyWrapper mRotationPolicyWrapper;
private final DeviceStateManager mDeviceStateManager;
private final Executor mMainExecutor;
- private final DeviceStateRotationLockSettingsManager mDeviceStateRotationLockSettingsManager;
+ private final DeviceStateAutoRotateSettingManager mDeviceStateAutoRotateSettingManager;
private final DeviceStateRotationLockSettingControllerLogger mLogger;
// On registration for DeviceStateCallback, we will receive a callback with the current state
@@ -65,13 +64,13 @@ public final class DeviceStateRotationLockSettingController
RotationPolicyWrapper rotationPolicyWrapper,
DeviceStateManager deviceStateManager,
@Main Executor executor,
- DeviceStateRotationLockSettingsManager deviceStateRotationLockSettingsManager,
+ DeviceStateAutoRotateSettingManager deviceStateAutoRotateSettingManager,
DeviceStateRotationLockSettingControllerLogger logger,
DumpManager dumpManager) {
mRotationPolicyWrapper = rotationPolicyWrapper;
mDeviceStateManager = deviceStateManager;
mMainExecutor = executor;
- mDeviceStateRotationLockSettingsManager = deviceStateRotationLockSettingsManager;
+ mDeviceStateAutoRotateSettingManager = deviceStateAutoRotateSettingManager;
mLogger = logger;
dumpManager.registerDumpable(this);
}
@@ -86,14 +85,14 @@ public final class DeviceStateRotationLockSettingController
mDeviceStateManager.registerCallback(mMainExecutor, mDeviceStateCallback);
mDeviceStateAutoRotateSettingListener = () ->
readPersistedSetting("deviceStateRotationLockChange", mDeviceState);
- mDeviceStateRotationLockSettingsManager.registerListener(
+ mDeviceStateAutoRotateSettingManager.registerListener(
mDeviceStateAutoRotateSettingListener);
} else {
if (mDeviceStateCallback != null) {
mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
}
if (mDeviceStateAutoRotateSettingListener != null) {
- mDeviceStateRotationLockSettingsManager.unregisterListener(
+ mDeviceStateAutoRotateSettingManager.unregisterListener(
mDeviceStateAutoRotateSettingListener);
}
}
@@ -102,7 +101,7 @@ public final class DeviceStateRotationLockSettingController
@Override
public void onRotationLockStateChanged(boolean newRotationLocked, boolean affordanceVisible) {
int deviceState = mDeviceState;
- boolean currentRotationLocked = mDeviceStateRotationLockSettingsManager
+ boolean currentRotationLocked = mDeviceStateAutoRotateSettingManager
.isRotationLocked(deviceState);
mLogger.logRotationLockStateChanged(deviceState, newRotationLocked, currentRotationLocked);
if (deviceState == -1) {
@@ -117,7 +116,7 @@ public final class DeviceStateRotationLockSettingController
private void saveNewRotationLockSetting(boolean isRotationLocked) {
int deviceState = mDeviceState;
mLogger.logSaveNewRotationLockSetting(isRotationLocked, deviceState);
- mDeviceStateRotationLockSettingsManager.updateSetting(deviceState, isRotationLocked);
+ mDeviceStateAutoRotateSettingManager.updateSetting(deviceState, isRotationLocked);
}
private void updateDeviceState(@NonNull DeviceState state) {
@@ -139,7 +138,7 @@ public final class DeviceStateRotationLockSettingController
private void readPersistedSetting(String caller, int state) {
int rotationLockSetting =
- mDeviceStateRotationLockSettingsManager.getRotationLockSetting(state);
+ mDeviceStateAutoRotateSettingManager.getRotationLockSetting(state);
boolean shouldBeLocked = rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_LOCKED;
boolean isLocked = mRotationPolicyWrapper.isRotationLocked();
@@ -167,7 +166,7 @@ public final class DeviceStateRotationLockSettingController
@Override
public void dump(@NonNull PrintWriter printWriter, @NonNull String[] args) {
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter);
- mDeviceStateRotationLockSettingsManager.dump(pw);
+ mDeviceStateAutoRotateSettingManager.dump(printWriter, null);
pw.println("DeviceStateRotationLockSettingController");
pw.increaseIndent();
pw.println("mDeviceState: " + mDeviceState);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index d1e807f18196..e2a6c195af38 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -18,13 +18,20 @@ package com.android.systemui.statusbar.policy.dagger;
import android.content.Context;
import android.content.res.Resources;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
import android.os.UserManager;
import com.android.internal.R;
-import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
+import com.android.settingslib.devicestate.AndroidSecureSettings;
+import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManager;
+import com.android.settingslib.devicestate.DeviceStateAutoRotateSettingManagerProvider;
+import com.android.settingslib.devicestate.PosturesHelper;
+import com.android.settingslib.devicestate.SecureSettings;
import com.android.settingslib.notification.modes.ZenIconLoader;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.log.LogBuffer;
@@ -222,12 +229,34 @@ public interface StatusBarPolicyModule {
return controller;
}
- /** Returns a singleton instance of DeviceStateRotationLockSettingsManager */
+ /** */
+ @SysUISingleton
+ @Provides
+ static SecureSettings provideAndroidSecureSettings(Context context) {
+ return new AndroidSecureSettings(context.getContentResolver());
+ }
+
+ /** */
@SysUISingleton
@Provides
- static DeviceStateRotationLockSettingsManager provideAutoRotateSettingsManager(
- Context context) {
- return DeviceStateRotationLockSettingsManager.getInstance(context);
+ static PosturesHelper providePosturesHelper(Context context,
+ DeviceStateManager deviceStateManager) {
+ return new PosturesHelper(context, deviceStateManager);
+ }
+
+ /** Returns a singleton instance of DeviceStateAutoRotateSettingManager based on auto-rotate
+ * refactor flag. */
+ @SysUISingleton
+ @Provides
+ static DeviceStateAutoRotateSettingManager provideAutoRotateSettingsManager(
+ Context context,
+ @Background Executor bgExecutor,
+ SecureSettings secureSettings,
+ @Main Handler mainHandler,
+ PosturesHelper posturesHelper
+ ) {
+ return DeviceStateAutoRotateSettingManagerProvider.createInstance(context, bgExecutor,
+ secureSettings, mainHandler, posturesHelper);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
index ecfcb29a9944..eae310ed169b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.window
import android.content.Context
import android.view.View
import android.view.ViewGroup
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import android.view.WindowManager
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController
@@ -84,7 +84,7 @@ interface StatusBarWindowController {
fun interface Factory {
fun create(
context: Context,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
statusBarConfigurationController: StatusBarConfigurationController,
contentInsetsProvider: StatusBarContentInsetsProvider,
): StatusBarWindowController
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
index 25972ac2bedf..77cd58111b94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerImpl.java
@@ -47,7 +47,6 @@ import android.view.WindowManager;
import androidx.annotation.NonNull;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.animation.ActivityTransitionAnimator;
import com.android.systemui.animation.DelegateTransitionAnimatorController;
@@ -78,7 +77,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
private static final boolean DEBUG = false;
private final Context mContext;
- private final ViewCaptureAwareWindowManager mWindowManager;
+ private final WindowManager mWindowManager;
private final StatusBarConfigurationController mStatusBarConfigurationController;
private final IWindowManager mIWindowManager;
private final StatusBarContentInsetsProvider mContentInsetsProvider;
@@ -100,7 +99,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
public StatusBarWindowControllerImpl(
@Assisted Context context,
@InternalWindowViewInflater StatusBarWindowViewInflater statusBarWindowViewInflater,
- @Assisted ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @Assisted WindowManager windowManager,
@Assisted StatusBarConfigurationController statusBarConfigurationController,
IWindowManager iWindowManager,
@Assisted StatusBarContentInsetsProvider contentInsetsProvider,
@@ -108,7 +107,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
Optional<UnfoldTransitionProgressProvider> unfoldTransitionProgressProvider,
@Main Executor mainExecutor) {
mContext = context;
- mWindowManager = viewCaptureAwareWindowManager;
+ mWindowManager = windowManager;
mStatusBarConfigurationController = statusBarConfigurationController;
mIWindowManager = iWindowManager;
mContentInsetsProvider = contentInsetsProvider;
@@ -406,7 +405,7 @@ public class StatusBarWindowControllerImpl implements StatusBarWindowController
@Override
StatusBarWindowControllerImpl create(
@NonNull Context context,
- @NonNull ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
+ @NonNull WindowManager windowManager,
@NonNull StatusBarConfigurationController statusBarConfigurationController,
@NonNull StatusBarContentInsetsProvider contentInsetsProvider);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
index 39afc38dad11..74c028a8c7ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.window
import android.content.Context
import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
@@ -42,7 +41,6 @@ constructor(
@Background backgroundApplicationScope: CoroutineScope,
private val controllerFactory: StatusBarWindowController.Factory,
private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository,
- private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory,
private val statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
private val statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
displayRepository: DisplayRepository,
@@ -67,11 +65,9 @@ constructor(
statusBarConfigurationControllerStore.forDisplay(displayId) ?: return null
val contentInsetsProvider =
statusBarContentInsetsProviderStore.forDisplay(displayId) ?: return null
- val viewCaptureAwareWindowManager =
- viewCaptureAwareWindowManagerFactory.create(statusBarDisplayContext.windowManager)
return controllerFactory.create(
statusBarDisplayContext.context,
- viewCaptureAwareWindowManager,
+ statusBarDisplayContext.windowManager,
statusBarConfigurationController,
contentInsetsProvider,
)
@@ -89,7 +85,7 @@ class SingleDisplayStatusBarWindowControllerStore
@Inject
constructor(
context: Context,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
factory: StatusBarWindowControllerImpl.Factory,
statusBarConfigurationControllerStore: StatusBarConfigurationControllerStore,
statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore,
@@ -98,7 +94,7 @@ constructor(
PerDisplayStore<StatusBarWindowController> by SingleDisplayStore(
factory.create(
context,
- viewCaptureAwareWindowManager,
+ windowManager,
statusBarConfigurationControllerStore.defaultDisplay,
statusBarContentInsetsProviderStore.defaultDisplay,
)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index 635576743462..ea5f422f977d 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -32,7 +32,6 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -70,7 +69,7 @@ import java.io.PrintWriter
abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger<T>>(
internal val context: Context,
internal val logger: U,
- internal val windowManager: ViewCaptureAwareWindowManager,
+ internal val windowManager: WindowManager,
@Main private val mainExecutor: DelayableExecutor,
private val accessibilityManager: AccessibilityManager,
private val configurationController: ConfigurationController,
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index 9b9cba9186f4..b6f54331b29f 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -29,6 +29,7 @@ import android.view.View
import android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
import android.view.View.ACCESSIBILITY_LIVE_REGION_NONE
import android.view.ViewGroup
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.ImageView
@@ -37,7 +38,6 @@ import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.annotation.VisibleForTesting
import com.android.app.animation.Interpolators
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.widget.CachingIconView
import com.android.systemui.Gefingerpoken
import com.android.systemui.classifier.FalsingCollector
@@ -81,7 +81,7 @@ open class ChipbarCoordinator
constructor(
context: Context,
logger: ChipbarLogger,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
@@ -100,7 +100,7 @@ constructor(
TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
context,
logger,
- viewCaptureAwareWindowManager,
+ windowManager,
mainExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt
index c11e4c507914..00c6ffd861da 100644
--- a/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt
+++ b/packages/SystemUI/src/com/android/systemui/topwindoweffects/TopLevelWindowEffects.kt
@@ -21,7 +21,6 @@ import android.graphics.PixelFormat
import android.view.Gravity
import android.view.WindowInsets
import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -38,7 +37,7 @@ import javax.inject.Inject
class TopLevelWindowEffects @Inject constructor(
@Application private val context: Context,
@Application private val applicationScope: CoroutineScope,
- private val windowManager: ViewCaptureAwareWindowManager,
+ private val windowManager: WindowManager,
private val squeezeEffectInteractor: SqueezeEffectInteractor,
private val keyEventInteractor: KeyEventInteractor,
private val viewModelFactory: SqueezeEffectViewModel.Factory
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
index 1cc7a3185a5d..5541c50fb650 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
@@ -50,7 +50,7 @@ class DisplaySwitchLatencyLogger {
onScreenTurningOnToOnDrawnMs,
onDrawnToOnScreenTurnedOnMs,
trackingResult,
- screenWakelockstatus
+ screenWakelockStatus,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
index 5800d5ed41c6..336e8d172ad4 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -36,11 +36,11 @@ import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.power.shared.model.WakefulnessModel
import com.android.systemui.power.shared.model.WakefulnessState
import com.android.systemui.shared.system.SysUiStatsLog
-import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.CORRUPTED
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.SUCCESS
import com.android.systemui.unfold.DisplaySwitchLatencyTracker.TrackingResult.TIMED_OUT
import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.data.repository.ScreenTimeoutPolicyRepository
import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.util.Compile
@@ -80,6 +80,7 @@ constructor(
private val context: Context,
private val deviceStateRepository: DeviceStateRepository,
private val powerInteractor: PowerInteractor,
+ private val screenTimeoutPolicyRepository: ScreenTimeoutPolicyRepository,
private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
private val animationStatusRepository: AnimationStatusRepository,
private val keyguardInteractor: KeyguardInteractor,
@@ -287,7 +288,18 @@ constructor(
log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
instantForTrack(TAG) { "fromFoldableDeviceState=$fromFoldableDeviceState" }
- return copy(fromFoldableDeviceState = fromFoldableDeviceState)
+ val screenTimeoutActive = screenTimeoutPolicyRepository.screenTimeoutActive.value
+ val screenWakelockStatus =
+ if (screenTimeoutActive) {
+ NO_SCREEN_WAKELOCKS
+ } else {
+ HAS_SCREEN_WAKELOCKS
+ }
+
+ return copy(
+ fromFoldableDeviceState = fromFoldableDeviceState,
+ screenWakelockStatus = screenWakelockStatus
+ )
}
private fun DisplaySwitchLatencyEvent.withAfterFields(
@@ -344,7 +356,7 @@ constructor(
val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN,
val trackingResult: Int =
SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TRACKING_RESULT__UNKNOWN_RESULT,
- val screenWakelockstatus: Int =
+ val screenWakelockStatus: Int =
SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_UNKNOWN,
)
@@ -372,5 +384,10 @@ constructor(
SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
private const val FOLDABLE_DEVICE_STATE_FLIPPED =
SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
+
+ private const val HAS_SCREEN_WAKELOCKS =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_HAS_SCREEN_WAKELOCKS
+ private const val NO_SCREEN_WAKELOCKS =
+ SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__SCREEN_WAKELOCK_STATUS__SCREEN_WAKELOCK_STATUS_NO_WAKELOCKS
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/data/repository/ScreenTimeoutPolicyRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/ScreenTimeoutPolicyRepository.kt
new file mode 100644
index 000000000000..797939447464
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/ScreenTimeoutPolicyRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.unfold.data.repository
+
+import android.os.PowerManager
+import android.view.Display
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/** Repository to get screen timeout updates */
+@SysUISingleton
+class ScreenTimeoutPolicyRepository
+@Inject
+constructor(
+ private val powerManager: PowerManager,
+ @Background private val executor: Executor,
+ @Background private val scope: CoroutineScope,
+) {
+
+ /** Stores true if there is an active screen timeout */
+ val screenTimeoutActive: StateFlow<Boolean> =
+ conflatedCallbackFlow {
+ val listener =
+ PowerManager.ScreenTimeoutPolicyListener { screenTimeoutPolicy ->
+ trySend(screenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_ACTIVE)
+ }
+ powerManager.addScreenTimeoutPolicyListener(
+ Display.DEFAULT_DISPLAY,
+ executor,
+ listener,
+ )
+ awaitClose {
+ powerManager.removeScreenTimeoutPolicyListener(
+ Display.DEFAULT_DISPLAY,
+ listener,
+ )
+ }
+ }
+ .stateIn(scope, started = SharingStarted.Eagerly, initialValue = true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
index d4fb5634bd1d..e16a51a6f6fa 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/source/UserRecord.kt
@@ -42,6 +42,8 @@ data class UserRecord(
@JvmField val isSwitchToEnabled: Boolean = false,
/** Whether this record represents an option to add another supervised user to the device. */
@JvmField val isAddSupervisedUser: Boolean = false,
+ /** Whether this record represents an option to sign out of the current user. */
+ @JvmField val isSignOut: Boolean = false,
/**
* An enforcing admin, if the user action represented by this record is disabled by the admin.
* If not disabled, this is `null`.
@@ -49,7 +51,7 @@ data class UserRecord(
@JvmField val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin? = null,
/** Whether this record is to go to the Settings page to manage users. */
- @JvmField val isManageUsers: Boolean = false
+ @JvmField val isManageUsers: Boolean = false,
) {
/** Returns a new instance of [UserRecord] with its [isCurrent] set to the given value. */
fun copyWithIsCurrent(isCurrent: Boolean): UserRecord {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 163288b25b28..b82aefc1ac1c 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -38,6 +38,7 @@ import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Flags.switchUserOnBg
+import com.android.systemui.Flags.userSwitcherAddSignOutOption
import com.android.systemui.SystemUISecondaryUserService
import com.android.systemui.animation.Expandable
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -110,6 +111,7 @@ constructor(
private val uiEventLogger: UiEventLogger,
private val userRestrictionChecker: UserRestrictionChecker,
private val processWrapper: ProcessWrapper,
+ private val userLogoutInteractor: UserLogoutInteractor,
) {
/**
* Defines interface for classes that can be notified when the state of users on the device is
@@ -242,6 +244,12 @@ constructor(
) {
add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
}
+ if (
+ userSwitcherAddSignOutOption() &&
+ userLogoutInteractor.isLogoutEnabled.value
+ ) {
+ add(UserActionModel.SIGN_OUT)
+ }
}
}
.flowOn(backgroundDispatcher)
@@ -261,7 +269,8 @@ constructor(
action = it,
selectedUserId = selectedUserInfo.id,
isRestricted =
- it != UserActionModel.ENTER_GUEST_MODE &&
+ it != UserActionModel.SIGN_OUT &&
+ it != UserActionModel.ENTER_GUEST_MODE &&
it != UserActionModel.NAVIGATE_TO_USER_MANAGEMENT &&
!settings.isAddUsersFromLockscreen,
)
@@ -499,6 +508,10 @@ constructor(
Intent(Settings.ACTION_USER_SETTINGS),
/* dismissShade= */ true,
)
+ UserActionModel.SIGN_OUT -> {
+ dismissDialog()
+ applicationScope.launch { userLogoutInteractor.logOut() }
+ }
}
}
@@ -583,9 +596,10 @@ constructor(
actionType = action,
isRestricted = isRestricted,
isSwitchToEnabled =
- canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
- // If the user is auto-created is must not be currently resetting.
- !(isGuestUserAutoCreated && isGuestUserResetting),
+ action == UserActionModel.SIGN_OUT ||
+ (canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
+ // If the user is auto-created is must not be currently resetting.
+ !(isGuestUserAutoCreated && isGuestUserResetting)),
userRestrictionChecker = userRestrictionChecker,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
index 80139bd6ac0c..23ca4ceda2de 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/data/LegacyUserDataHelper.kt
@@ -74,6 +74,7 @@ object LegacyUserDataHelper {
isGuest = actionType == UserActionModel.ENTER_GUEST_MODE,
isAddUser = actionType == UserActionModel.ADD_USER,
isAddSupervisedUser = actionType == UserActionModel.ADD_SUPERVISED_USER,
+ isSignOut = actionType == UserActionModel.SIGN_OUT,
isRestricted = isRestricted,
isSwitchToEnabled = isSwitchToEnabled,
enforcedAdmin =
@@ -94,6 +95,7 @@ object LegacyUserDataHelper {
record.isAddSupervisedUser -> UserActionModel.ADD_SUPERVISED_USER
record.isGuest -> UserActionModel.ENTER_GUEST_MODE
record.isManageUsers -> UserActionModel.NAVIGATE_TO_USER_MANAGEMENT
+ record.isSignOut -> UserActionModel.SIGN_OUT
else -> error("Not a known action: $record")
}
}
@@ -105,15 +107,14 @@ object LegacyUserDataHelper {
private fun getEnforcedAdmin(
context: Context,
selectedUserId: Int,
- userRestrictionChecker: UserRestrictionChecker
+ userRestrictionChecker: UserRestrictionChecker,
): EnforcedAdmin? {
val admin =
userRestrictionChecker.checkIfRestrictionEnforced(
context,
UserManager.DISALLOW_ADD_USER,
selectedUserId,
- )
- ?: return null
+ ) ?: return null
return if (
!userRestrictionChecker.hasBaseUserRestriction(
@@ -145,11 +146,6 @@ object LegacyUserDataHelper {
val unscaledOrNull = manager.getUserIcon(userInfo.id) ?: return null
val avatarSize = context.resources.getDimensionPixelSize(R.dimen.max_avatar_size)
- return Bitmap.createScaledBitmap(
- unscaledOrNull,
- avatarSize,
- avatarSize,
- /* filter= */ true,
- )
+ return Bitmap.createScaledBitmap(unscaledOrNull, avatarSize, avatarSize, /* filter= */ true)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 09cef1ed64fc..e7a3c23e9119 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -41,6 +41,7 @@ object LegacyUserUiHelper {
isAddUser: Boolean,
isGuest: Boolean,
isAddSupervisedUser: Boolean,
+ isSignOut: Boolean,
isTablet: Boolean = false,
isManageUsers: Boolean,
): Int {
@@ -52,6 +53,8 @@ object LegacyUserUiHelper {
com.android.settingslib.R.drawable.ic_account_circle
} else if (isAddSupervisedUser) {
com.android.settingslib.R.drawable.ic_add_supervised_user
+ } else if (isSignOut) {
+ com.android.internal.R.drawable.ic_logout
} else if (isManageUsers) {
R.drawable.ic_manage_users
} else {
@@ -81,6 +84,7 @@ object LegacyUserUiHelper {
isGuestUserResetting = isGuestUserResetting,
isAddUser = record.isAddUser,
isAddSupervisedUser = record.isAddSupervisedUser,
+ isSignOut = record.isSignOut,
isTablet = isTablet,
isManageUsers = record.isManageUsers,
)
@@ -111,10 +115,11 @@ object LegacyUserUiHelper {
isGuestUserResetting: Boolean,
isAddUser: Boolean,
isAddSupervisedUser: Boolean,
+ isSignOut: Boolean,
isTablet: Boolean = false,
isManageUsers: Boolean,
): Int {
- check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers)
+ check(isGuest || isAddUser || isAddSupervisedUser || isManageUsers || isSignOut)
return when {
isGuest && isGuestUserAutoCreated && isGuestUserResetting ->
@@ -124,6 +129,7 @@ object LegacyUserUiHelper {
isGuest -> com.android.internal.R.string.guest_name
isAddUser -> com.android.settingslib.R.string.user_add_user
isAddSupervisedUser -> R.string.add_user_supervised
+ isSignOut -> com.android.internal.R.string.global_action_logout
isManageUsers -> R.string.manage_users
else -> error("This should never happen!")
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt
index 823bf74dc0f0..7f67d7691bf5 100644
--- a/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/shared/model/UserActionModel.kt
@@ -22,4 +22,5 @@ enum class UserActionModel {
ADD_USER,
ADD_SUPERVISED_USER,
NAVIGATE_TO_USER_MANAGEMENT,
+ SIGN_OUT,
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
index 4089889f4b1e..2e3af1c7ad00 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModel.kt
@@ -129,9 +129,7 @@ constructor(
cancelButtonClicked || executedActionFinish || userSwitched
}
- private fun toViewModel(
- model: UserModel,
- ): UserViewModel {
+ private fun toViewModel(model: UserModel): UserViewModel {
return UserViewModel(
viewKey = model.id,
name =
@@ -152,14 +150,13 @@ constructor(
)
}
- private fun toViewModel(
- model: UserActionModel,
- ): UserActionViewModel {
+ private fun toViewModel(model: UserActionModel): UserActionViewModel {
return UserActionViewModel(
viewKey = model.ordinal.toLong(),
iconResourceId =
LegacyUserUiHelper.getUserSwitcherActionIconResourceId(
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+ isSignOut = model == UserActionModel.SIGN_OUT,
isAddUser = model == UserActionModel.ADD_USER,
isGuest = model == UserActionModel.ENTER_GUEST_MODE,
isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
@@ -171,6 +168,7 @@ constructor(
isGuestUserAutoCreated = guestUserInteractor.isGuestUserAutoCreated,
isGuestUserResetting = guestUserInteractor.isGuestUserResetting,
isAddSupervisedUser = model == UserActionModel.ADD_SUPERVISED_USER,
+ isSignOut = model == UserActionModel.SIGN_OUT,
isAddUser = model == UserActionModel.ADD_USER,
isManageUsers = model == UserActionModel.NAVIGATE_TO_USER_MANAGEMENT,
isTablet = true,
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index 70fd5ab767d0..f1a556353273 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -20,7 +20,6 @@ import android.graphics.Rect
import android.util.IndentingPrintWriter
import android.view.View
import android.view.ViewGroup
-import dagger.Lazy
import java.io.PrintWriter
/** [Sequence] that yields all of the direct children of this [ViewGroup] */
@@ -56,11 +55,6 @@ val View.boundsOnScreen: Rect
return bounds
}
-/** Extension method to convert [dagger.Lazy] to [kotlin.Lazy] for object of any class [T]. */
-fun <T> Lazy<T>.toKotlinLazy(): kotlin.Lazy<T> {
- return lazy { this.get() }
-}
-
/**
* Returns whether this [Collection] contains exactly all [elements].
*
diff --git a/packages/SystemUI/src/com/android/systemui/util/display/DisplayHelper.java b/packages/SystemUI/src/com/android/systemui/util/display/DisplayHelper.java
index 8acd6535e751..757b2d973312 100644
--- a/packages/SystemUI/src/com/android/systemui/util/display/DisplayHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/util/display/DisplayHelper.java
@@ -21,6 +21,8 @@ import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.WindowManager;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
+
import javax.inject.Inject;
/**
@@ -29,14 +31,17 @@ import javax.inject.Inject;
public class DisplayHelper {
private final Context mContext;
private final DisplayManager mDisplayManager;
+ private final WindowManagerProvider mWindowManagerProvider;
/**
* Default constructor.
*/
@Inject
- public DisplayHelper(Context context, DisplayManager displayManager) {
+ public DisplayHelper(Context context, DisplayManager displayManager,
+ WindowManagerProvider windowManagerProvider) {
mContext = context;
mDisplayManager = displayManager;
+ mWindowManagerProvider = windowManagerProvider;
}
@@ -45,9 +50,8 @@ public class DisplayHelper {
*/
public Rect getMaxBounds(int displayId, int windowContextType) {
final Display display = mDisplayManager.getDisplay(displayId);
- WindowManager windowManager = mContext.createDisplayContext(display)
- .createWindowContext(windowContextType, null)
- .getSystemService(WindowManager.class);
+ WindowManager windowManager = mWindowManagerProvider.getWindowManager(mContext
+ .createDisplayContext(display).createWindowContext(windowContextType, null));
return windowManager.getMaximumWindowMetrics().getBounds();
}
}
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 3ac6c7bc0c6b..457b6ac42f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialog.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog
import android.content.Context
import android.os.Bundle
+import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -57,8 +58,10 @@ constructor(
attributes.apply {
title = "VolumeDialog" // Not the same as Window#setTitle
}
- setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ setGravity(Gravity.END)
}
+ setCancelable(false)
setCanceledOnTouchOutside(true)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
index 938e313771ad..b06a3b7784f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/VolumeDialogPlugin.kt
@@ -68,6 +68,7 @@ constructor(
viewModel.isShowingSafetyWarning
.mapLatest { isShowingSafetyWarning ->
if (isShowingSafetyWarning) {
+ viewModel.onSafetyWarningDialogShown()
showSafetyWarningVisibility { viewModel.onSafetyWarningDismissed() }
}
}
@@ -76,6 +77,7 @@ constructor(
viewModel.csdWarning
.mapLatest { csdWarning ->
if (csdWarning != null) {
+ viewModel.onCsdWarningDialogShown()
showCsdWarningDialog(csdWarning, viewModel.csdWarningConfigModel.actions) {
viewModel.onCsdWarningDismissed()
}
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 bd23e8cce82d..4ed25859fb86 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
@@ -108,6 +108,7 @@ constructor(
/** Resets current dialog timeout. */
fun resetDismissTimeout() {
+ controller.userActivity()
mutableDismissDialogEvents.tryEmit(Unit)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt
index fb9884cf4341..ad24f08a7214 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ringer/ui/util/RingerDrawerConstraintsUtils.kt
@@ -111,7 +111,7 @@ private fun ConstraintSet.adjustOpenConstraintsForDrawer(
view.id,
ConstraintSet.START,
motionLayout.context.resources.getDimensionPixelSize(
- R.dimen.volume_dialog_ringer_drawer_margin
+ R.dimen.volume_dialog_background_margin
),
)
}
@@ -121,7 +121,7 @@ private fun ConstraintSet.adjustOpenConstraintsForDrawer(
view.id,
ConstraintSet.END,
motionLayout.context.resources.getDimensionPixelSize(
- R.dimen.volume_dialog_components_spacing
+ R.dimen.volume_dialog_ringer_drawer_buttons_spacing
),
)
} else {
@@ -140,7 +140,7 @@ private fun ConstraintSet.adjustOpenConstraintsForDrawer(
view.id,
ConstraintSet.BOTTOM,
motionLayout.context.resources.getDimensionPixelSize(
- R.dimen.volume_dialog_components_spacing
+ R.dimen.volume_dialog_ringer_drawer_buttons_spacing
),
)
} else {
@@ -158,10 +158,10 @@ private fun ConstraintSet.adjustOpenConstraintsForDrawer(
R.dimen.volume_dialog_ringer_drawer_button_size
) * (motionLayout.childCount - 1)) +
(motionLayout.context.resources.getDimensionPixelSize(
- R.dimen.volume_dialog_ringer_drawer_margin
+ R.dimen.volume_dialog_background_margin
) * 2) +
(motionLayout.context.resources.getDimensionPixelSize(
- R.dimen.volume_dialog_components_spacing
+ R.dimen.volume_dialog_ringer_drawer_buttons_spacing
) * (motionLayout.childCount - 2))
ORIENTATION_PORTRAIT ->
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index db4b8ef5aef7..dc41e90faeaf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -92,6 +92,8 @@ private fun VolumeDialogSlider(
SliderDefaults.colors(
activeTickColor = MaterialTheme.colorScheme.surfaceContainerHighest,
inactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledActiveTickColor = MaterialTheme.colorScheme.surfaceContainerHighest,
+ disabledInactiveTrackColor = MaterialTheme.colorScheme.surfaceContainerHighest,
)
val collectedSliderStateModel by viewModel.state.collectAsStateWithLifecycle(null)
val sliderStateModel = collectedSliderStateModel ?: return
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
index 6d16300bf56e..87cc1830af28 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModel.kt
@@ -70,10 +70,18 @@ constructor(
val isShowingSafetyWarning: Flow<Boolean> = dialogSafetyWarningInteractor.isShowingSafetyWarning
val csdWarning: Flow<Int?> = dialogCsdWarningInteractor.csdWarning
+ fun onSafetyWarningDialogShown() {
+ dialogVisibilityInteractor.resetDismissTimeout()
+ }
+
fun onSafetyWarningDismissed() {
dialogSafetyWarningInteractor.onSafetyWarningDismissed()
}
+ fun onCsdWarningDialogShown() {
+ dialogVisibilityInteractor.resetDismissTimeout()
+ }
+
fun onCsdWarningDismissed() {
dialogCsdWarningInteractor.onCsdWarningDismissed()
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index b1c6455a6e57..107f670eae8d 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -42,7 +42,6 @@ import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
-import android.view.WindowManager;
import androidx.annotation.NonNull;
@@ -50,6 +49,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.LongRunning;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -71,6 +71,7 @@ public class ImageWallpaper extends WallpaperService {
private boolean mPagesComputed = false;
private final UserTracker mUserTracker;
+ private final WindowManagerProvider mWindowManagerProvider;
// used to handle WallpaperService messages (e.g. DO_ATTACH, MSG_UPDATE_SURFACE)
// and to receive WallpaperService callbacks (e.g. onCreateEngine, onSurfaceRedrawNeeded)
@@ -84,10 +85,12 @@ public class ImageWallpaper extends WallpaperService {
private static final int DELAY_UNLOAD_BITMAP = 2000;
@Inject
- public ImageWallpaper(@LongRunning DelayableExecutor longExecutor, UserTracker userTracker) {
+ public ImageWallpaper(@LongRunning DelayableExecutor longExecutor, UserTracker userTracker,
+ WindowManagerProvider windowManagerProvider) {
super();
mLongExecutor = longExecutor;
mUserTracker = userTracker;
+ mWindowManagerProvider = windowManagerProvider;
}
@Override
@@ -552,8 +555,7 @@ public class ImageWallpaper extends WallpaperService {
}
private void getDisplaySizeAndUpdateColorExtractor() {
- Rect window = getDisplayContext()
- .getSystemService(WindowManager.class)
+ Rect window = mWindowManagerProvider.getWindowManager(getDisplayContext())
.getCurrentWindowMetrics()
.getBounds();
mWallpaperLocalColorExtractor.setDisplayDimensions(window.width(), window.height());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 0769ada805a2..645e306eed07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -80,8 +80,6 @@ import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository;
@@ -104,7 +102,6 @@ import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.commandline.CommandRegistry;
import com.android.systemui.statusbar.events.PrivacyDotViewController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.FakeThreadFactory;
import com.android.systemui.util.kotlin.JavaAdapter;
@@ -112,8 +109,6 @@ import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.FakeSystemClock;
-import kotlin.Lazy;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -132,7 +127,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private ScreenDecorations mScreenDecorations;
private WindowManager mWindowManager;
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private DisplayManager mDisplayManager;
private SecureSettings mSecureSettings;
private FakeExecutor mExecutor;
@@ -179,8 +173,6 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private CutoutDecorProviderFactory mCutoutFactory;
@Mock
private JavaAdapter mJavaAdapter;
- @Mock
- private Lazy<ViewCapture> mLazyViewCapture;
private FakeFacePropertyRepository mFakeFacePropertyRepository =
new FakeFacePropertyRepository();
@@ -254,14 +246,12 @@ public class ScreenDecorationsTest extends SysuiTestCase {
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
mFakeFacePropertyRepository));
- mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, false);
mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
- mViewCaptureAwareWindowManager, mMainHandler, mExecutor) {
+ mWindowManager, mMainHandler, mExecutor) {
@Override
public void start() {
super.start();
@@ -1276,7 +1266,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mPrivacyDotDecorProviderFactory, mFaceScanningProviderFactory,
new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
mFakeFacePropertyRepository, mJavaAdapter, mCameraProtectionLoader,
- mViewCaptureAwareWindowManager, mMainHandler, mExecutor);
+ mWindowManager, mMainHandler, mExecutor);
screenDecorations.start();
when(mContext.getDisplay()).thenReturn(mDisplay);
when(mDisplay.getDisplayInfo(any())).thenAnswer(new Answer<Boolean>() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index 6d75c4ca3a38..d3d4e24001cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -43,13 +43,13 @@ import android.view.accessibility.IRemoteMagnificationAnimationCallback;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import org.junit.Before;
import org.junit.Test;
@@ -94,7 +94,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
@Mock
private IWindowManager mIWindowManager;
@Mock
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private WindowManagerProvider mWindowManagerProvider;
private IMagnificationConnection mIMagnificationConnection;
private MagnificationImpl mMagnification;
@@ -116,8 +116,7 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
mModeSwitchesController, mSysUiState, mLauncherProxyService, mSecureSettings,
mDisplayTracker, getContext().getSystemService(DisplayManager.class),
- mA11yLogger, mIWindowManager, mAccessibilityManager,
- mViewCaptureAwareWindowManager);
+ mA11yLogger, mIWindowManager, mAccessibilityManager, mWindowManagerProvider);
mMagnification.mWindowMagnificationControllerSupplier =
new FakeWindowMagnificationControllerSupplier(
mContext.getSystemService(DisplayManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
index 8bfd2545ff2b..ae96e8fe7b8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/MagnificationTest.java
@@ -50,13 +50,13 @@ import android.view.accessibility.IMagnificationConnectionCallback;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.LauncherProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import org.junit.Before;
import org.junit.Test;
@@ -98,7 +98,7 @@ public class MagnificationTest extends SysuiTestCase {
@Mock
private IWindowManager mIWindowManager;
@Mock
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
+ private WindowManagerProvider mWindowManagerProvider;
@Before
public void setUp() throws Exception {
@@ -132,8 +132,7 @@ public class MagnificationTest extends SysuiTestCase {
mCommandQueue, mModeSwitchesController,
mSysUiState, mLauncherProxyService, mSecureSettings, mDisplayTracker,
getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager,
- getContext().getSystemService(AccessibilityManager.class),
- mViewCaptureAwareWindowManager);
+ getContext().getSystemService(AccessibilityManager.class), mWindowManagerProvider);
mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index a0f5b2214f80..ac0378b093c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -51,8 +51,6 @@ import android.view.animation.AccelerateInterpolator;
import android.window.InputTransferToken;
import androidx.test.filters.LargeTest;
-
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
@@ -112,8 +110,6 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
SysUiState mSysUiState;
@Mock
SecureSettings mSecureSettings;
- @Mock
- ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private SpyWindowMagnificationController mController;
private WindowMagnificationController mSpyController;
private WindowMagnificationAnimationController mWindowMagnificationAnimationController;
@@ -167,7 +163,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mSecureSettings,
scvhSupplier,
mSfVsyncFrameProvider,
- mViewCaptureAwareWindowManager);
+ mWindowManager);
mSpyController = mController.getSpyController();
}
@@ -1023,7 +1019,7 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
SecureSettings secureSettings,
Supplier<SurfaceControlViewHost> scvhSupplier,
SfVsyncFrameCallbackProvider sfVsyncFrameProvider,
- ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
+ WindowManager windowManager) {
super(
context,
handler,
@@ -1033,7 +1029,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
callback,
sysUiState,
secureSettings,
- scvhSupplier);
+ scvhSupplier,
+ windowManager);
mSpyController = Mockito.mock(WindowMagnificationController.class);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 5b32b922d377..c8e0b14152fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -238,7 +238,8 @@ public class WindowMagnificationControllerTest extends SysuiTestCase {
mWindowMagnifierCallback,
mSysUiState,
mSecureSettings,
- scvhSupplier);
+ scvhSupplier,
+ mWindowManager);
verify(mMirrorWindowControl).setWindowDelegate(
any(MirrorWindowControl.MirrorWindowDelegate.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 45b9f4ad2322..dfc3ff2791b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -65,8 +65,6 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView;
@@ -74,8 +72,6 @@ import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarW
import com.android.systemui.res.R;
import com.android.systemui.util.settings.SecureSettings;
-import kotlin.Lazy;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -104,8 +100,6 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
private SecureSettings mSecureSettings;
@Mock
private WindowMagnificationSettingsCallback mWindowMagnificationSettingsCallback;
- @Mock
- private Lazy<ViewCapture> mLazyViewCapture;
private TestableWindowManager mWindowManager;
private WindowMagnificationSettings mWindowMagnificationSettings;
private MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@@ -123,6 +117,7 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
final WindowManager wm = mContext.getSystemService(WindowManager.class);
mWindowManager = spy(new TestableWindowManager(wm));
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
+
mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).then(
@@ -130,11 +125,9 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase {
when(mSecureSettings.getFloatForUser(anyString(), anyFloat(), anyInt())).then(
returnsSecondArg());
- ViewCaptureAwareWindowManager vwm = new ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, /* isViewCaptureEnabled= */ false);
mWindowMagnificationSettings = new WindowMagnificationSettings(mContext,
mWindowMagnificationSettingsCallback, mSfVsyncFrameProvider,
- mSecureSettings, vwm);
+ mSecureSettings, mWindowManager);
mSettingView = mWindowMagnificationSettings.getSettingView();
mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index bc9d4c7fa0e6..b81cbe48b4ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -38,8 +38,6 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.bluetooth.HearingAidDeviceManager;
@@ -51,8 +49,6 @@ import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.util.settings.SecureSettings;
-import kotlin.Lazy;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@@ -78,7 +74,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
private Context mContextWrapper;
private WindowManager mWindowManager;
- private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
private AccessibilityManager mAccessibilityManager;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private AccessibilityFloatingMenuController mController;
@@ -93,8 +88,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
@Mock
private SecureSettings mSecureSettings;
@Mock
- private Lazy<ViewCapture> mLazyViewCapture;
- @Mock
private NavigationModeController mNavigationModeController;
@Mock
private HearingAidDeviceManager mHearingAidDeviceManager;
@@ -110,8 +103,6 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
};
mWindowManager = mContext.getSystemService(WindowManager.class);
- mViewCaptureAwareWindowManager = new ViewCaptureAwareWindowManager(mWindowManager,
- mLazyViewCapture, /* isViewCaptureEnabled= */ false);
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
mTestableLooper = TestableLooper.get(this);
@@ -172,8 +163,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
- mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
- mNavigationModeController, mHearingAidDeviceManager);
+ mAccessibilityManager, mSecureSettings, mNavigationModeController,
+ mHearingAidDeviceManager);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
@@ -200,8 +191,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
- mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
- mNavigationModeController, mHearingAidDeviceManager);
+ mAccessibilityManager, mSecureSettings, mNavigationModeController,
+ mHearingAidDeviceManager);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -215,8 +206,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
enableAccessibilityFloatingMenuConfig();
mController = setUpController();
mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
- mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
- mNavigationModeController, mHearingAidDeviceManager);
+ mAccessibilityManager, mSecureSettings, mNavigationModeController,
+ mHearingAidDeviceManager);
captureKeyguardUpdateMonitorCallback();
mKeyguardCallback.onUserUnlocked();
mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -362,18 +353,15 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
private AccessibilityFloatingMenuController setUpController() {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
- final ViewCaptureAwareWindowManager viewCaptureAwareWindowManager =
- new ViewCaptureAwareWindowManager(windowManager, mLazyViewCapture,
- /* isViewCaptureEnabled= */ false);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
final FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext);
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
final AccessibilityFloatingMenuController controller =
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
- viewCaptureAwareWindowManager, displayManager, mAccessibilityManager,
- mTargetsObserver, mModeObserver, mHearingAidDeviceManager,
- mKeyguardUpdateMonitor, mSecureSettings, displayTracker,
- mNavigationModeController, new Handler(mTestableLooper.getLooper()));
+ displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
+ mHearingAidDeviceManager, mKeyguardUpdateMonitor, mSecureSettings,
+ displayTracker, mNavigationModeController,
+ new Handler(mTestableLooper.getLooper()));
controller.init();
return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
index 60345a358bac..dfb6afe8bd12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ActivityTransitionAnimatorTest.kt
@@ -249,10 +249,12 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
var factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(2, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
factory = controllerFactory(controller)
underTest.register(factory.cookie, factory, testScope)
assertEquals(4, testShellTransitions.remotes.size)
+ assertTrue(testShellTransitions.remotesForTakeover.isEmpty())
}
}
@@ -350,6 +352,7 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
val controller = createController()
val runner = underTest.createEphemeralRunner(controller)
runner.onAnimationCancelled()
+ waitForIdleSync()
runner.onAnimationStart(
TRANSIT_NONE,
emptyArray(),
@@ -357,12 +360,13 @@ class ActivityTransitionAnimatorTest : SysuiTestCase() {
emptyArray(),
iCallback,
)
-
waitForIdleSync()
+
verify(controller).onTransitionAnimationCancelled()
verify(controller, never()).onTransitionAnimationStart(anyBoolean())
verify(listener).onTransitionAnimationCancelled()
verify(listener, never()).onTransitionAnimationStart()
+ verify(iCallback).onAnimationFinished()
assertNull(runner.delegate)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index a1d038ad8554..a9e6a3ebdb30 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -28,8 +28,6 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityTransitionAnimator
@@ -62,7 +60,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
-import dagger.Lazy
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -98,7 +95,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
@Mock private lateinit var inflater: LayoutInflater
@Mock private lateinit var windowManager: WindowManager
- @Mock private lateinit var lazyViewCapture: kotlin.Lazy<ViewCapture>
@Mock private lateinit var accessibilityManager: AccessibilityManager
@Mock private lateinit var statusBarStateController: StatusBarStateController
@Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -160,11 +156,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() {
UdfpsControllerOverlay(
context,
inflater,
- ViewCaptureAwareWindowManager(
- windowManager,
- lazyViewCapture,
- isViewCaptureEnabled = false,
- ),
+ windowManager,
accessibilityManager,
statusBarStateController,
statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 2c70249bcb06..bf79d11b2fb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -1472,7 +1472,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
whenever(kosmos.udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any()))
.thenReturn("Direction")
- kosmos.promptViewModel.onAnnounceAccessibilityHint(
+ kosmos.promptViewModel.onUpdateAccessibilityHint(
obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
true,
)
@@ -1497,7 +1497,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
whenever(kosmos.udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any()))
.thenReturn("Direction")
- kosmos.promptViewModel.onAnnounceAccessibilityHint(
+ kosmos.promptViewModel.onUpdateAccessibilityHint(
obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
true,
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index 57b397cfca7e..034bab855faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -23,13 +23,11 @@ import android.view.WindowManager
import android.view.WindowMetrics
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
-import com.android.systemui.res.R
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -42,12 +40,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.Mock
+import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -62,7 +60,6 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var windowMetrics: WindowMetrics
- @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
private val systemClock = FakeSystemClock()
@Before
@@ -71,9 +68,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
`when`(featureFlags.isEnabled(Flags.CHARGING_RIPPLE)).thenReturn(true)
controller = WiredChargingRippleController(
commandRegistry, batteryController, configurationController,
- featureFlags, context, windowManager,
- ViewCaptureAwareWindowManager(windowManager,
- lazyViewCapture, isViewCaptureEnabled = false), systemClock, uiEventLogger)
+ featureFlags, context, windowManager, systemClock, uiEventLogger)
rippleView.setupShader()
controller.rippleView = rippleView // Replace the real ripple view with a mock instance
controller.registerCallbacks()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 206654abcaaa..41fdaa74e57b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -73,10 +73,10 @@ import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
@@ -175,7 +175,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock BroadcastDispatcher mBroadcastDispatcher;
private @Mock DismissCallbackRegistry mDismissCallbackRegistry;
private @Mock DumpManager mDumpManager;
- private @Mock ViewCaptureAwareWindowManager mWindowManager;
+ private @Mock WindowManager mWindowManager;
private @Mock IActivityManager mActivityManager;
private @Mock ConfigurationController mConfigurationController;
private @Mock PowerManager mPowerManager;
@@ -1500,6 +1500,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKosmos.getKeyguardInteractor(),
mKeyguardTransitionBootInteractor,
mKosmos::getCommunalSceneInteractor,
+ mKosmos::getCommunalSettingsInteractor,
mock(WindowManagerOcclusionManager.class));
mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
index 86f7966d4ada..d6b778fe2bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTestKt.kt
@@ -35,8 +35,14 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.activityTransitionAnimator
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.data.repository.batteryRepository
+import com.android.systemui.common.data.repository.fake
+import com.android.systemui.communal.data.model.FEATURE_AUTO_OPEN
+import com.android.systemui.communal.data.model.SuppressionReason
import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
+import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
@@ -81,8 +87,11 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
/** Kotlin version of KeyguardViewMediatorTest to allow for coroutine testing. */
@SmallTest
@@ -152,6 +161,7 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
keyguardInteractor,
keyguardTransitionBootInteractor,
{ communalSceneInteractor },
+ { communalSettingsInteractor },
mock<WindowManagerOcclusionManager>(),
)
}
@@ -164,6 +174,10 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
@Test
fun doKeyguardTimeout_changesCommunalScene() =
kosmos.runTest {
+ // Hub is enabled and hub condition is active.
+ setCommunalV2Enabled(true)
+ enableHubOnCharging()
+
// doKeyguardTimeout message received.
val timeoutOptions = Bundle()
timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
@@ -174,4 +188,56 @@ class KeyguardViewMediatorTestKt : SysuiTestCase() {
assertThat(communalSceneRepository.currentScene.value)
.isEqualTo(CommunalScenes.Communal)
}
+
+ @Test
+ fun doKeyguardTimeout_communalNotAvailable_sleeps() =
+ kosmos.runTest {
+ // Hub disabled.
+ setCommunalV2Enabled(false)
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ @Test
+ fun doKeyguardTimeout_hubConditionNotActive_sleeps() =
+ kosmos.runTest {
+ // Communal enabled, but hub condition set to never.
+ setCommunalV2Enabled(true)
+ disableHubShowingAutomatically()
+
+ // doKeyguardTimeout message received.
+ val timeoutOptions = Bundle()
+ timeoutOptions.putBoolean(KeyguardViewMediator.EXTRA_TRIGGER_HUB, true)
+ underTest.doKeyguardTimeout(timeoutOptions)
+ testableLooper.processAllMessages()
+
+ // Sleep is requested.
+ verify(powerManager)
+ .goToSleep(anyOrNull(), eq(PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON), eq(0))
+
+ // Hub scene is not changed.
+ assertThat(communalSceneRepository.currentScene.value).isEqualTo(CommunalScenes.Blank)
+ }
+
+ private fun Kosmos.enableHubOnCharging() {
+ communalSettingsInteractor.setSuppressionReasons(emptyList())
+ batteryRepository.fake.setDevicePluggedIn(true)
+ }
+
+ private fun Kosmos.disableHubShowingAutomatically() {
+ communalSettingsInteractor.setSuppressionReasons(
+ listOf(SuppressionReason.ReasonUnknown(FEATURE_AUTO_OPEN))
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index df24bff43c08..78a4fbecabe8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -31,11 +31,13 @@ import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel
+import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
@@ -68,6 +70,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
underTest =
DefaultDeviceEntrySection(
TestScope().backgroundScope,
+ testKosmos().testDispatcher,
authController,
windowManager,
context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index c7beb158c2de..add87686bc9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -20,8 +20,8 @@ import android.content.Context
import android.os.Handler
import android.os.PowerManager
import android.view.ViewGroup
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -35,7 +35,7 @@ class FakeMediaTttChipControllerReceiver(
commandQueue: CommandQueue,
context: Context,
logger: MediaTttReceiverLogger,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
@@ -53,7 +53,7 @@ class FakeMediaTttChipControllerReceiver(
commandQueue,
context,
logger,
- viewCaptureAwareWindowManager,
+ windowManager,
mainExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 378dd452d030..1aa6ac67ec27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -31,8 +31,6 @@ import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -75,8 +73,6 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var rippleController: MediaTttReceiverRippleController
- @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
- private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
private lateinit var commandQueueCallback: CommandQueue.Callbacks
private lateinit var fakeAppIconDrawable: Drawable
private lateinit var uiEventLoggerFake: UiEventLoggerFake
@@ -114,18 +110,12 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
fakeWakeLockBuilder = WakeLockFake.Builder(context)
fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
- viewCaptureAwareWindowManager =
- ViewCaptureAwareWindowManager(
- windowManager,
- lazyViewCapture,
- isViewCaptureEnabled = false,
- )
controllerReceiver =
FakeMediaTttChipControllerReceiver(
commandQueue,
context,
logger,
- viewCaptureAwareWindowManager,
+ windowManager,
fakeExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index c90ac5993c31..18de32e80da6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -34,7 +34,6 @@ import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.SysuiTestCase
@@ -100,7 +99,6 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var vibratorHelper: VibratorHelper
@Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler
- @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
private lateinit var chipbarCoordinator: ChipbarCoordinator
@@ -145,11 +143,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
ChipbarCoordinator(
context,
chipbarLogger,
- ViewCaptureAwareWindowManager(
- windowManager,
- lazyViewCapture,
- isViewCaptureEnabled = false,
- ),
+ windowManager,
fakeExecutor,
accessibilityManager,
configurationController,
@@ -1476,7 +1470,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
private fun ViewGroup.getUndoButton(): View = this.requireViewById(R.id.end_button)
private fun ChipStateSender.getExpectedStateText(
- otherDeviceName: String = OTHER_DEVICE_NAME
+ otherDeviceName: String = OTHER_DEVICE_NAME,
): String? {
return this.getChipTextString(context, otherDeviceName).loadText(context)
}
@@ -1487,7 +1481,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
routeInfo,
- null,
+ null
)
}
@@ -1497,7 +1491,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
commandQueueCallback.updateMediaTapToTransferSenderDisplay(
StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
routeInfo,
- null,
+ null
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index b4db6da2000a..b169cc12f08a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -27,7 +27,6 @@ import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.jank.Cuj
import com.android.internal.util.LatencyTracker
import com.android.systemui.SysuiTestCase
@@ -64,7 +63,7 @@ class BackPanelControllerTest : SysuiTestCase() {
private var triggerThreshold: Float = 0.0f
private val touchSlop = ViewConfiguration.get(context).scaledEdgeSlop
@Mock private lateinit var vibratorHelper: VibratorHelper
- @Mock private lateinit var viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager
+ @Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var configurationController: ConfigurationController
@Mock private lateinit var latencyTracker: LatencyTracker
private val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
@@ -79,7 +78,7 @@ class BackPanelControllerTest : SysuiTestCase() {
mBackPanelController =
BackPanelController(
context,
- viewCaptureAwareWindowManager,
+ windowManager,
ViewConfiguration.get(context),
Handler.createAsync(testableLooper.looper),
systemClock,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 8a4f1ad13b78..5596cc7ee7da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -38,8 +38,6 @@ import com.android.systemui.notetask.NoteTaskEntryPoint.TAIL_BUTTON
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
@@ -52,11 +50,14 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.anyList
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
/** atest SystemUITests:NoteTaskInitializerTest */
@OptIn(InternalNoteTaskApi::class)
@@ -180,6 +181,18 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
@Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
+ fun initialize_keyGestureTypeOpenNotes_isRegistered() {
+ val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
+ underTest.initialize()
+ verify(inputManager)
+ .registerKeyGestureEventHandler(
+ eq(listOf(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)),
+ any(),
+ )
+ }
+
+ @Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
fun handlesShortcut_keyGestureTypeOpenNotes() {
val gestureEvent =
KeyGestureEvent.Builder()
@@ -189,12 +202,12 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
val callback = withArgCaptor {
- verify(inputManager).registerKeyGestureEventHandler(capture())
+ verify(inputManager).registerKeyGestureEventHandler(anyList(), capture())
}
- assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue()
-
+ callback.handleKeyGestureEvent(gestureEvent, null)
executor.runAllReady()
+
verify(controller).showNoteTask(eq(KEYBOARD_SHORTCUT))
}
@@ -203,19 +216,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
fun handlesShortcut_stylusTailButton() {
val gestureEvent =
KeyGestureEvent.Builder()
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
+ .setKeycodes(intArrayOf(KEYCODE_STYLUS_BUTTON_TAIL))
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES)
.setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
.build()
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
val callback = withArgCaptor {
- verify(inputManager).registerKeyGestureEventHandler(capture())
+ verify(inputManager).registerKeyGestureEventHandler(anyList(), capture())
}
- assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isTrue()
-
+ callback.handleKeyGestureEvent(gestureEvent, null)
executor.runAllReady()
+
verify(controller).showNoteTask(eq(TAIL_BUTTON))
}
@@ -224,19 +237,19 @@ internal class NoteTaskInitializerTest : SysuiTestCase() {
fun ignoresUnrelatedShortcuts() {
val gestureEvent =
KeyGestureEvent.Builder()
- .setKeycodes(intArrayOf(KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL))
+ .setKeycodes(intArrayOf(KEYCODE_STYLUS_BUTTON_TAIL))
.setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
.setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
.build()
val underTest = createUnderTest(isEnabled = true, bubbles = bubbles)
underTest.initialize()
val callback = withArgCaptor {
- verify(inputManager).registerKeyGestureEventHandler(capture())
+ verify(inputManager).registerKeyGestureEventHandler(anyList(), capture())
}
- assertThat(callback.handleKeyGestureEvent(gestureEvent, null)).isFalse()
-
+ callback.handleKeyGestureEvent(gestureEvent, null)
executor.runAllReady()
+
verify(controller, never()).showNoteTask(any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
index 7e42ec7e83b1..1551375f6879 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -37,6 +37,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
@@ -63,7 +64,7 @@ class DragAndDropTest : SysuiTestCase() {
columns = 4,
largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
- onAddTile = {},
+ onAddTile = { _, _ -> },
onRemoveTile = {},
onSetTiles = onSetTiles,
onResize = { _, _ -> },
@@ -84,7 +85,7 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0], DragType.Add)
+ listState.onStarted(TestEditTiles[0], DragType.Move)
// Tile is being dragged, it should be replaced with a placeholder
composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
@@ -103,6 +104,45 @@ class DragAndDropTest : SysuiTestCase() {
}
@Test
+ fun nonRemovableDraggedTile_removeHeaderShouldNotExist() {
+ val nonRemovableTile = createEditTile("tileA", isRemovable = false)
+ val listState = EditTileListState(listOf(nonRemovableTile), columns = 4, largeTilesSpan = 2)
+ composeRule.setContent { EditTileGridUnderTest(listState) {} }
+ composeRule.waitForIdle()
+
+ listState.onStarted(nonRemovableTile, DragType.Move)
+
+ // Tile is being dragged, it should be replaced with a placeholder
+ composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
+
+ // Remove drop zone should not appear
+ composeRule.onNodeWithText("Remove").assertDoesNotExist()
+ }
+
+ @Test
+ fun droppedNonRemovableDraggedTile_shouldStayInGrid() {
+ val nonRemovableTile = createEditTile("tileA", isRemovable = false)
+ val listState = EditTileListState(listOf(nonRemovableTile), columns = 4, largeTilesSpan = 2)
+ composeRule.setContent { EditTileGridUnderTest(listState) {} }
+ composeRule.waitForIdle()
+
+ listState.onStarted(nonRemovableTile, DragType.Move)
+
+ // Tile is being dragged, it should be replaced with a placeholder
+ composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
+
+ // Remove drop zone should not appear
+ composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+ // Drop tile outside of the grid
+ listState.movedOutOfBounds()
+ listState.onDrop()
+
+ // Tile A is still in the grid
+ composeRule.assertGridContainsExactly(CURRENT_TILES_GRID_TEST_TAG, listOf("tileA"))
+ }
+
+ @Test
fun draggedTile_shouldChangePosition() {
var tiles by mutableStateOf(TestEditTiles)
val listState = EditTileListState(tiles, columns = 4, largeTilesSpan = 2)
@@ -113,7 +153,11 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0], DragType.Add)
+ listState.onStarted(TestEditTiles[0], DragType.Move)
+
+ // Remove drop zone should appear
+ composeRule.onNodeWithText("Remove").assertExists()
+
listState.onTargeting(1, false)
listState.onDrop()
@@ -141,7 +185,11 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(TestEditTiles[0], DragType.Add)
+ listState.onStarted(TestEditTiles[0], DragType.Move)
+
+ // Remove drop zone should appear
+ composeRule.onNodeWithText("Remove").assertExists()
+
listState.movedOutOfBounds()
listState.onDrop()
@@ -169,7 +217,11 @@ class DragAndDropTest : SysuiTestCase() {
}
composeRule.waitForIdle()
- listState.onStarted(createEditTile("tile_new"), DragType.Add)
+ listState.onStarted(createEditTile("tile_new", isRemovable = false), DragType.Add)
+
+ // Remove drop zone should appear
+ composeRule.onNodeWithText("Remove").assertExists()
+
// Insert after tileD, which is at index 4
// [ a ] [ b ] [ c ] [ empty ]
// [ tile d ] [ e ]
@@ -193,7 +245,10 @@ class DragAndDropTest : SysuiTestCase() {
private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
- private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+ private fun createEditTile(
+ tileSpec: String,
+ isRemovable: Boolean = true,
+ ): SizedTile<EditTileViewModel> {
return SizedTileImpl(
EditTileViewModel(
tileSpec = TileSpec.create(tileSpec),
@@ -205,7 +260,8 @@ class DragAndDropTest : SysuiTestCase() {
label = AnnotatedString(tileSpec),
appName = null,
isCurrent = true,
- availableEditActions = emptySet(),
+ availableEditActions =
+ if (isRemovable) setOf(AvailableEditActions.REMOVE) else emptySet(),
category = TileCategory.UNKNOWN,
),
getWidth(tileSpec),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
index 9d4a425c678b..acb441c3765d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/EditModeTest.kt
@@ -23,6 +23,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.doubleClick
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onAllNodesWithText
@@ -30,6 +31,7 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.text.AnnotatedString
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -40,6 +42,7 @@ import com.android.systemui.common.shared.model.Icon
import com.android.systemui.qs.panels.shared.model.SizedTile
import com.android.systemui.qs.panels.shared.model.SizedTileImpl
import com.android.systemui.qs.panels.ui.compose.infinitegrid.DefaultEditTileGrid
+import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.shared.model.TileCategory
@@ -53,8 +56,8 @@ class EditModeTest : SysuiTestCase() {
@get:Rule val composeRule = createComposeRule()
@Composable
- private fun EditTileGridUnderTest() {
- var tiles by remember { mutableStateOf(TestEditTiles) }
+ private fun EditTileGridUnderTest(sizedTiles: List<SizedTile<EditTileViewModel>>) {
+ var tiles by remember { mutableStateOf(sizedTiles) }
val (currentTiles, otherTiles) = tiles.partition { it.tile.isCurrent }
val listState = EditTileListState(currentTiles, columns = 4, largeTilesSpan = 2)
@@ -65,7 +68,7 @@ class EditModeTest : SysuiTestCase() {
columns = 4,
largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
- onAddTile = { tiles = tiles.add(it) },
+ onAddTile = { spec, _ -> tiles = tiles.add(spec) },
onRemoveTile = { tiles = tiles.remove(it) },
onSetTiles = {},
onResize = { _, _ -> },
@@ -77,7 +80,7 @@ class EditModeTest : SysuiTestCase() {
@Test
fun clickAvailableTile_shouldAdd() {
- composeRule.setContent { EditTileGridUnderTest() }
+ composeRule.setContent { EditTileGridUnderTest(TestEditTiles) }
composeRule.waitForIdle()
composeRule.onNodeWithContentDescription("tileF").performClick() // Tap to add
@@ -93,7 +96,7 @@ class EditModeTest : SysuiTestCase() {
@Test
fun clickRemoveTarget_shouldRemoveSelection() {
- composeRule.setContent { EditTileGridUnderTest() }
+ composeRule.setContent { EditTileGridUnderTest(TestEditTiles) }
composeRule.waitForIdle()
// Selects first "tileA", i.e. the one in the current grid
@@ -110,6 +113,36 @@ class EditModeTest : SysuiTestCase() {
)
}
+ @Test
+ fun selectNonRemovableTile_removeTargetShouldHide() {
+ val nonRemovableTile = createEditTile("tileA", isRemovable = false)
+ composeRule.setContent { EditTileGridUnderTest(listOf(nonRemovableTile)) }
+ composeRule.waitForIdle()
+
+ // Selects first "tileA", i.e. the one in the current grid
+ composeRule.onAllNodesWithText("tileA").onFirst().performClick()
+
+ // Assert the remove target isn't shown
+ composeRule.onNodeWithText("Remove").assertDoesNotExist()
+ }
+
+ @Test
+ fun placementMode_shouldRepositionTile() {
+ composeRule.setContent { EditTileGridUnderTest(TestEditTiles) }
+ composeRule.waitForIdle()
+
+ // Double tap first "tileA", i.e. the one in the current grid
+ composeRule.onAllNodesWithText("tileA").onFirst().performTouchInput { doubleClick() }
+
+ // Tap on tileE to position tileA in its spot
+ composeRule.onAllNodesWithText("tileE").onFirst().performClick()
+
+ // Assert tileA moved to tileE's position
+ composeRule.assertCurrentTilesGridContainsExactly(
+ listOf("tileB", "tileC", "tileD_large", "tileE", "tileA")
+ )
+ }
+
private fun ComposeContentTestRule.assertCurrentTilesGridContainsExactly(specs: List<String>) =
assertGridContainsExactly(CURRENT_TILES_GRID_TEST_TAG, specs)
@@ -148,6 +181,7 @@ class EditModeTest : SysuiTestCase() {
private fun createEditTile(
tileSpec: String,
isCurrent: Boolean = true,
+ isRemovable: Boolean = true,
): SizedTile<EditTileViewModel> {
return SizedTileImpl(
EditTileViewModel(
@@ -160,7 +194,8 @@ class EditModeTest : SysuiTestCase() {
label = AnnotatedString(tileSpec),
appName = null,
isCurrent = isCurrent,
- availableEditActions = emptySet(),
+ availableEditActions =
+ if (isRemovable) setOf(AvailableEditActions.REMOVE) else emptySet(),
category = TileCategory.UNKNOWN,
),
getWidth(tileSpec),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
index 5e76000cc7f0..274c44cef949 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/ui/compose/ResizingTest.kt
@@ -69,7 +69,7 @@ class ResizingTest : SysuiTestCase() {
columns = 4,
largeTilesSpan = 4,
modifier = Modifier.fillMaxSize(),
- onAddTile = {},
+ onAddTile = { _, _ -> },
onRemoveTile = {},
onSetTiles = {},
onResize = onResize,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java
index e4a4953063bb..e949c8a10c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java
@@ -62,7 +62,6 @@ import android.view.WindowManager;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.wifi.WifiUtils;
@@ -162,7 +161,7 @@ public class InternetDetailsContentControllerTest extends SysuiTestCase {
@Mock
InternetDetailsContentController.InternetDialogCallback mInternetDialogCallback;
@Mock
- private ViewCaptureAwareWindowManager mWindowManager;
+ private WindowManager mWindowManager;
@Mock
private ToastFactory mToastFactory;
@Mock
@@ -234,9 +233,8 @@ public class InternetDetailsContentControllerTest extends SysuiTestCase {
mSubscriptionManager, mTelephonyManager, mWifiManager,
mConnectivityManager, mHandler, mExecutor, mBroadcastDispatcher,
mock(KeyguardUpdateMonitor.class), mGlobalSettings, mKeyguardStateController,
- mWindowManager, mToastFactory, mWorkerHandler,
- mCarrierConfigTracker, mLocationController, mDialogTransitionAnimator,
- mWifiStateWorker, mFlags);
+ mWindowManager, mToastFactory, mWorkerHandler, mCarrierConfigTracker,
+ mLocationController, mDialogTransitionAnimator, mWifiStateWorker, mFlags);
mSubscriptionManager.addOnSubscriptionsChangedListener(mExecutor,
mInternetDetailsContentController.mOnSubscriptionsChangedListener);
mInternetDetailsContentController.onStart(mInternetDialogCallback, true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index 997cf417fe10..f4d0c26f12ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -18,9 +18,11 @@ package com.android.systemui.reardisplay
import android.hardware.devicestate.feature.flags.Flags.FLAG_DEVICE_STATE_RDM_V2
import android.hardware.display.rearDisplay
+import android.os.fakeExecutorHandler
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.Display
+import android.view.accessibility.accessibilityManager
import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
@@ -62,6 +64,8 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
kosmos.rearDisplayInnerDialogDelegateFactory,
kosmos.testScope,
kosmos.keyguardUpdateMonitor,
+ kosmos.accessibilityManager,
+ kosmos.fakeExecutorHandler,
)
@Before
@@ -69,7 +73,7 @@ class RearDisplayCoreStartableTest : SysuiTestCase() {
whenever(kosmos.rearDisplay.flags).thenReturn(Display.FLAG_REAR)
whenever(kosmos.rearDisplay.displayAdjustments)
.thenReturn(mContext.display.displayAdjustments)
- whenever(kosmos.rearDisplayInnerDialogDelegateFactory.create(any(), any()))
+ whenever(kosmos.rearDisplayInnerDialogDelegateFactory.create(any(), any(), any()))
.thenReturn(mockDelegate)
whenever(mockDelegate.createDialog()).thenReturn(mockDialog)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
index fc7661666825..477e5babdcc3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayInnerDialogDelegateTest.kt
@@ -17,7 +17,10 @@
package com.android.systemui.reardisplay
import android.testing.TestableLooper
+import android.view.View
+import android.widget.Button
import android.widget.SeekBar
+import android.widget.TextView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.msdl.msdlPlayer
@@ -28,6 +31,7 @@ import com.android.systemui.res.R
import com.android.systemui.statusbar.phone.systemUIDialogDotFactory
import com.android.systemui.testKosmos
import com.android.systemui.util.time.systemClock
+import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Test
@@ -49,6 +53,7 @@ class RearDisplayInnerDialogDelegateTest : SysuiTestCase() {
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -68,6 +73,7 @@ class RearDisplayInnerDialogDelegateTest : SysuiTestCase() {
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -78,6 +84,9 @@ class RearDisplayInnerDialogDelegateTest : SysuiTestCase() {
.apply {
show()
val seekbar = findViewById<SeekBar>(R.id.seekbar)
+ assertThat(seekbar.visibility).isEqualTo(View.VISIBLE)
+ assertThat(findViewById<TextView>(R.id.seekbar_instructions).visibility)
+ .isEqualTo(View.VISIBLE)
seekbar.progress = 50
seekbar.progress = 100
verify(mockCallback).run()
@@ -90,6 +99,7 @@ class RearDisplayInnerDialogDelegateTest : SysuiTestCase() {
RearDisplayInnerDialogDelegate(
kosmos.systemUIDialogDotFactory,
mContext,
+ false /* touchExplorationEnabled */,
kosmos.vibratorHelper,
kosmos.msdlPlayer,
kosmos.systemClock,
@@ -118,4 +128,33 @@ class RearDisplayInnerDialogDelegateTest : SysuiTestCase() {
// Progress is reset
verify(mockSeekbar).setProgress(eq(0))
}
+
+ @Test
+ fun testTouchExplorationEnabled() {
+ val mockCallback = mock<Runnable>()
+
+ RearDisplayInnerDialogDelegate(
+ kosmos.systemUIDialogDotFactory,
+ mContext,
+ true /* touchExplorationEnabled */,
+ kosmos.vibratorHelper,
+ kosmos.msdlPlayer,
+ kosmos.systemClock,
+ ) {
+ mockCallback.run()
+ }
+ .createDialog()
+ .apply {
+ show()
+ assertThat(findViewById<SeekBar>(R.id.seekbar).visibility).isEqualTo(View.GONE)
+ assertThat(findViewById<TextView>(R.id.seekbar_instructions).visibility)
+ .isEqualTo(View.GONE)
+
+ val cancelButton = findViewById<Button>(R.id.cancel_button)
+ assertThat(cancelButton.visibility).isEqualTo(View.VISIBLE)
+
+ cancelButton.performClick()
+ verify(mockCallback).run()
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 8045a13ff9be..07204ee814d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.google.android.material.bottomsheet.BottomSheetDialog;
@@ -61,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
@Mock private BottomSheetDialog mBottomSheetDialog;
@Mock WindowManager mWindowManager;
@Mock Handler mHandler;
+ @Mock WindowManagerProvider mWindowManagerProvider;
@Before
public void setUp() {
@@ -77,7 +79,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
public void toggle_isShowingTrue_instanceShouldBeNull() {
when(mBottomSheetDialog.isShowing()).thenReturn(true);
- mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
assertThat(mKeyboardShortcutListSearch.sInstance).isNull();
}
@@ -86,7 +88,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
public void toggle_isShowingFalse_showKeyboardShortcuts() {
when(mBottomSheetDialog.isShowing()).thenReturn(false);
- mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
@@ -96,7 +98,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
public void requestAppKeyboardShortcuts_callback_sanitisesIcons() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor =
ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class);
@@ -114,7 +116,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
public void requestImeKeyboardShortcuts_callback_sanitisesIcons() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
+ mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor =
ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
index 2cb9791cc159..0bd9b29a1a37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsReceiverTest.java
@@ -36,6 +36,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import org.junit.After;
import org.junit.Before;
@@ -59,6 +60,7 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
@Mock private KeyboardShortcuts mKeyboardShortcuts;
@Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
+ @Mock private WindowManagerProvider mWindowManagerProvider;
@Before
public void setUp() {
@@ -69,7 +71,8 @@ public class KeyboardShortcutsReceiverTest extends SysuiTestCase {
KeyboardShortcuts.sInstance = mKeyboardShortcuts;
KeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
- mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags));
+ mKeyboardShortcutsReceiver = spy(new KeyboardShortcutsReceiver(mFeatureFlags,
+ mWindowManagerProvider));
}
@Before
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index 20ecaf75c625..939f2b899dbe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -39,6 +39,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import org.junit.Before;
import org.junit.Rule;
@@ -67,6 +68,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Mock private Dialog mDialog;
@Mock WindowManager mWindowManager;
@Mock Handler mHandler;
+ @Mock WindowManagerProvider mWindowManagerProvider;
@Before
public void setUp() {
@@ -92,7 +94,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
public void toggle_isShowingTrue_instanceShouldBeNull() {
when(mDialog.isShowing()).thenReturn(true);
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
assertThat(KeyboardShortcuts.sInstance).isNull();
}
@@ -101,7 +103,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
public void toggle_isShowingFalse_showKeyboardShortcuts() {
when(mDialog.isShowing()).thenReturn(false);
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
@@ -131,7 +133,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Test
public void requestAppKeyboardShortcuts_callback_sanitisesIcons() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
emitAppShortcuts(singletonList(group), DEVICE_ID);
@@ -142,7 +144,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Test
public void requestImeKeyboardShortcuts_callback_sanitisesIcons() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
emitImeShortcuts(singletonList(group), DEVICE_ID);
@@ -153,7 +155,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Test
public void onImeAndAppShortcutsReceived_appShortcutsNull_doesNotCrash() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
emitImeShortcuts(singletonList(group), DEVICE_ID);
emitAppShortcuts(/* groups= */ null, DEVICE_ID);
@@ -162,7 +164,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Test
public void onImeAndAppShortcutsReceived_imeShortcutsNull_doesNotCrash() {
KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests();
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
emitAppShortcuts(singletonList(group), DEVICE_ID);
emitImeShortcuts(/* groups= */ null, DEVICE_ID);
@@ -170,7 +172,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
@Test
public void onImeAndAppShortcutsReceived_bothNull_doesNotCrash() {
- KeyboardShortcuts.toggle(mContext, DEVICE_ID);
+ KeyboardShortcuts.toggle(mContext, DEVICE_ID, mWindowManagerProvider);
emitImeShortcuts(/* groups= */ null, DEVICE_ID);
emitAppShortcuts(/* groups= */ null, DEVICE_ID);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 2ea4e7f67b3c..bc7ab9d4fe3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -582,7 +582,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
public void testIconScrollXAfterTranslationAndReset() throws Exception {
ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
- group.setDismissUsingRowTranslationX(false);
+ group.setDismissUsingRowTranslationX(false, false);
group.setTranslation(50);
assertEquals(50, -group.getEntry().getIcons().getShelfIcon().getScrollX());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index a7f3fdcb517e..0c5cbc299aee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -93,7 +93,6 @@ import android.view.WindowMetrics;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.compose.animation.scene.ObservableTransitionState;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
@@ -214,6 +213,7 @@ import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SystemSettings;
import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.utils.windowmanager.WindowManagerProvider;
import com.android.systemui.volume.VolumeComponent;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
import com.android.wm.shell.bubbles.Bubbles;
@@ -372,9 +372,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private GlanceableHubContainerController mGlanceableHubContainerController;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
- @Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
@Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
@Mock private QuickAccessWalletController mQuickAccessWalletController;
+ @Mock private WindowManager mWindowManager;
+ @Mock private WindowManagerProvider mWindowManagerProvider;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -642,8 +643,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mBrightnessMirrorShowingRepository,
mGlanceableHubContainerController,
mEmergencyGestureIntentFactory,
- mViewCaptureAwareWindowManager,
- mQuickAccessWalletController
+ mQuickAccessWalletController,
+ mWindowManager,
+ mWindowManagerProvider
);
mScreenLifecycle.addObserver(mCentralSurfaces.mScreenObserver);
mCentralSurfaces.initShadeVisibilityListener();
@@ -1363,15 +1365,13 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private void switchToScreenSize(int widthDp, int heightDp) {
WindowMetrics windowMetrics = Mockito.mock(WindowMetrics.class);
- WindowManager windowManager = Mockito.mock(WindowManager.class);
Configuration configuration = new Configuration();
configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
mContext.getOrCreateTestableResources().overrideConfiguration(configuration);
when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, widthDp, heightDp));
- when(windowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
- mContext.addMockSystemService(WindowManager.class, windowManager);
+ when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics);
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 68f66611c981..574b2c010a37 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -471,6 +471,51 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
+ fun onTouch_withMouseOnEndSideIcons_flagOn_propagatedToShadeDisplayPolicy() {
+ val view = createViewMock()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
+ val event = getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
+
+ val statusContainer = view.requireViewById<View>(R.id.system_icons)
+ statusContainer.dispatchTouchEvent(event)
+
+ verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
+ }
+
+ @Test
+ @EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
+ fun onTouch_withMouseOnStartSideIcons_flagOn_propagatedToShadeDisplayPolicy() {
+ val view = createViewMock()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
+ val event = getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
+
+ val statusContainer = view.requireViewById<View>(R.id.status_bar_start_side_content)
+ statusContainer.dispatchTouchEvent(event)
+
+ verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
+ }
+
+ @Test
+ @DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
+ fun onTouch_withMouseOnSystemIcons_flagOff_notPropagatedToShadeDisplayPolicy() {
+ val view = createViewMock()
+ InstrumentationRegistry.getInstrumentation().runOnMainSync {
+ controller = createAndInitController(view)
+ }
+ val event = getActionUpEventFromSource(InputDevice.SOURCE_MOUSE)
+
+ val statusContainer = view.requireViewById<View>(R.id.system_icons)
+ statusContainer.dispatchTouchEvent(event)
+
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any())
+ }
+
+ @Test
fun shadeIsExpandedOnStatusIconMouseClick() {
val view = createViewMock()
InstrumentationRegistry.getInstrumentation().runOnMainSync {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0d99c0e8cab8..320a87e7db17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -176,6 +176,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
private FakeKeyguardStateController mKeyguardStateController =
spy(new FakeKeyguardStateController());
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private final static String TEST_REASON = "reason";
@Mock
private ViewRootImpl mViewRootImpl;
@@ -272,14 +273,15 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.dismissWithAction(
action, cancelAction, false /* afterKeyguardGone */);
verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
- verify(mPrimaryBouncerInteractor).show(eq(true));
+ verify(mPrimaryBouncerInteractor).show(eq(true),
+ eq("StatusBarKeyguardViewManager#dismissWithAction"));
}
@Test
public void showPrimaryBouncer_onlyWhenShowing() {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */, TEST_REASON);
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean(), eq(TEST_REASON));
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
verify(mSceneInteractor, never()).changeScene(any(), any());
}
@@ -289,8 +291,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */, TEST_REASON);
+ verify(mPrimaryBouncerInteractor, never()).show(anyBoolean(), eq(TEST_REASON));
verify(mDeviceEntryInteractor, never()).attemptDeviceEntry();
verify(mSceneInteractor, never()).changeScene(any(), any());
}
@@ -298,8 +300,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Test
@DisableSceneContainer
public void showBouncer_showsTheBouncer() {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
- verify(mPrimaryBouncerInteractor).show(eq(true));
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */, TEST_REASON);
+ verify(mPrimaryBouncerInteractor).show(eq(true), eq(TEST_REASON));
}
@Test
@@ -344,19 +346,20 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
mKeyguardStateController.setCanDismissLockScreen(false);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncerInteractor).show(eq(false));
+ verify(mPrimaryBouncerInteractor).show(eq(false),
+ eq("StatusBarKeyguardViewManager#onPanelExpansionChanged"));
// But not when it's already visible
reset(mPrimaryBouncerInteractor);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncerInteractor, never()).show(eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false), eq(TEST_REASON));
// Or animating away
reset(mPrimaryBouncerInteractor);
when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
- verify(mPrimaryBouncerInteractor, never()).show(eq(false));
+ verify(mPrimaryBouncerInteractor, never()).show(eq(false), eq(TEST_REASON));
}
@Test
@@ -546,7 +549,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
// WHEN showBouncer is called
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true, TEST_REASON);
// THEN alt bouncer should be hidden
verify(mAlternateBouncerInteractor).hide();
@@ -571,10 +574,10 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
// WHEN showGenericBouncer is called
final boolean scrimmed = true;
- mStatusBarKeyguardViewManager.showBouncer(scrimmed);
+ mStatusBarKeyguardViewManager.showBouncer(scrimmed, TEST_REASON);
// THEN regular bouncer is shown
- verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
+ verify(mPrimaryBouncerInteractor).show(eq(scrimmed), eq(TEST_REASON));
}
@Test
@@ -835,7 +838,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
// WHEN request to show primary bouncer
- mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(true, TEST_REASON);
// THEN the scrim isn't updated from StatusBarKeyguardViewManager
verify(mCentralSurfaces, never()).updateScrimController();
@@ -847,9 +850,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
public void testShowBouncerOrKeyguard_needsFullScreen() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false, TEST_REASON);
verify(mCentralSurfaces).hideKeyguard();
- verify(mPrimaryBouncerInteractor).show(true);
+ verify(mPrimaryBouncerInteractor).show(true, TEST_REASON);
}
@Test
@@ -859,7 +862,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
// Returning false means unable to show the bouncer
- when(mPrimaryBouncerInteractor.show(true)).thenReturn(false);
+ when(mPrimaryBouncerInteractor.show(true, TEST_REASON)).thenReturn(false);
when(mKeyguardTransitionInteractor.getTransitionState().getValue().getTo())
.thenReturn(KeyguardState.LOCKSCREEN);
mStatusBarKeyguardViewManager.onStartedWakingUp();
@@ -868,8 +871,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
// Advance past reattempts
mStatusBarKeyguardViewManager.setAttemptsToShowBouncer(10);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false);
- verify(mPrimaryBouncerInteractor).show(true);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, false, TEST_REASON);
+ verify(mPrimaryBouncerInteractor).show(true, TEST_REASON);
verify(mCentralSurfaces).showKeyguard();
}
@@ -884,7 +887,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
reset(mCentralSurfaces);
reset(mPrimaryBouncerInteractor);
mStatusBarKeyguardViewManager.showBouncerOrKeyguard(
- /* hideBouncerWhenShowing= */true, false);
+ /* hideBouncerWhenShowing= */true, false, TEST_REASON);
verify(mCentralSurfaces).showKeyguard();
verify(mPrimaryBouncerInteractor).hide();
}
@@ -897,9 +900,9 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset, TEST_REASON);
verify(mCentralSurfaces, never()).hideKeyguard();
- verify(mPrimaryBouncerInteractor).show(true);
+ verify(mPrimaryBouncerInteractor).show(true, TEST_REASON);
}
@Test
@@ -909,24 +912,24 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.SimPin);
when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
- mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset);
+ mStatusBarKeyguardViewManager.showBouncerOrKeyguard(false, isFalsingReset, TEST_REASON);
verify(mCentralSurfaces, never()).hideKeyguard();
// Do not refresh the full screen bouncer if the call is from falsing
- verify(mPrimaryBouncerInteractor, never()).show(true);
+ verify(mPrimaryBouncerInteractor, never()).show(true, TEST_REASON);
}
@Test
@EnableSceneContainer
public void showBouncer_attemptDeviceEntry() {
- mStatusBarKeyguardViewManager.showBouncer(false);
+ mStatusBarKeyguardViewManager.showBouncer(false, TEST_REASON);
verify(mDeviceEntryInteractor).attemptDeviceEntry();
}
@Test
@EnableSceneContainer
public void showPrimaryBouncer() {
- mStatusBarKeyguardViewManager.showPrimaryBouncer(false);
+ mStatusBarKeyguardViewManager.showPrimaryBouncer(false, TEST_REASON);
verify(mSceneInteractor).showOverlay(eq(Overlays.Bouncer), anyString());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt
new file mode 100644
index 000000000000..5695df5c307d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionKairosAdapterTelephonySmokeTests.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileConnectionKairosAdapterTelephonySmokeTests : MobileConnectionTelephonySmokeTestsBase() {
+
+ var job: Job? = null
+ val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+ override fun recreateRepo(): MobileConnectionRepository {
+ lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+ job?.cancel()
+ Mockito.clearInvocations(telephonyManager)
+ job =
+ testScope.backgroundScope.launch {
+ kairosNetwork.activateSpec {
+ val repo = activated {
+ MobileConnectionRepositoryKairosImpl(
+ MobileConnectionRepositoryTest.SUB_1_ID,
+ context,
+ subscriptionModel.toState(),
+ MobileConnectionRepositoryTest.DEFAULT_NAME_MODEL,
+ MobileConnectionRepositoryTest.SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappings,
+ testDispatcher,
+ logger,
+ tableLogger,
+ flags,
+ )
+ }
+ adapter = MobileConnectionRepositoryKairosAdapter(repo, systemUiCarrierConfig)
+ Unit
+ }
+ }
+ testScope.runCurrent()
+ return adapter
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt
new file mode 100644
index 000000000000..0cb7c1eea268
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryKairosAdapterTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.activated
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class MobileConnectionRepositoryKairosAdapterTest : MobileConnectionRepositoryTest() {
+
+ var job: Job? = null
+ val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+ override fun recreateRepo(): MobileConnectionRepository {
+ lateinit var adapter: MobileConnectionRepositoryKairosAdapter
+ job?.cancel()
+ Mockito.clearInvocations(telephonyManager)
+ job =
+ testScope.backgroundScope.launch {
+ kairosNetwork.activateSpec {
+ val repo = activated {
+ MobileConnectionRepositoryKairosImpl(
+ SUB_1_ID,
+ context,
+ subscriptionModel.toState(),
+ DEFAULT_NAME_MODEL,
+ SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappings,
+ testDispatcher,
+ logger,
+ tableLogger,
+ flags,
+ )
+ }
+ adapter = MobileConnectionRepositoryKairosAdapter(repo, systemUiCarrierConfig)
+ Unit
+ }
+ }
+ testScope.runCurrent() // ensure the lateinit is set
+ return adapter
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index ed8be9b253ab..2636195c0021 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -88,6 +88,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionMod
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfigWithOverride
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.telephonyDisplayInfo
@@ -116,25 +117,49 @@ import org.mockito.kotlin.argumentCaptor
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
@RunWith(AndroidJUnit4::class)
-class MobileConnectionRepositoryTest : SysuiTestCase() {
- private lateinit var underTest: MobileConnectionRepositoryImpl
+class MobileConnectionRepositoryImplTest : MobileConnectionRepositoryTest() {
+ override fun recreateRepo(): MobileConnectionRepository =
+ MobileConnectionRepositoryImpl(
+ SUB_1_ID,
+ context,
+ subscriptionModel,
+ DEFAULT_NAME_MODEL,
+ SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappings,
+ testDispatcher,
+ logger,
+ tableLogger,
+ flags,
+ testScope.backgroundScope,
+ )
+}
+
+abstract class MobileConnectionRepositoryTest : SysuiTestCase() {
+
+ abstract fun recreateRepo(): MobileConnectionRepository
+
+ lateinit var underTest: MobileConnectionRepository
- private val flags =
+ protected val flags =
FakeFeatureFlagsClassic().also { it.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var telephonyManager: TelephonyManager
- @Mock private lateinit var logger: MobileInputLogger
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var context: Context
+ @Mock protected lateinit var connectivityManager: ConnectivityManager
+ @Mock protected lateinit var telephonyManager: TelephonyManager
+ @Mock protected lateinit var logger: MobileInputLogger
+ @Mock protected lateinit var tableLogger: TableLogBuffer
+ @Mock protected lateinit var context: Context
- private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+ protected val mobileMappings = FakeMobileMappingsProxy()
+ protected val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ protected val testDispatcher = UnconfinedTestDispatcher()
+ protected val testScope = TestScope(testDispatcher)
- private val subscriptionModel: MutableStateFlow<SubscriptionModel?> =
+ protected val subscriptionModel: MutableStateFlow<SubscriptionModel?> =
MutableStateFlow(
SubscriptionModel(
subscriptionId = SUB_1_ID,
@@ -144,28 +169,11 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
)
@Before
- fun setUp() {
+ fun setUpBase() {
MockitoAnnotations.initMocks(this)
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
- underTest =
- MobileConnectionRepositoryImpl(
- SUB_1_ID,
- context,
- subscriptionModel,
- DEFAULT_NAME_MODEL,
- SEP,
- connectivityManager,
- telephonyManager,
- systemUiCarrierConfig,
- fakeBroadcastDispatcher,
- mobileMappings,
- testDispatcher,
- logger,
- tableLogger,
- flags,
- testScope.backgroundScope,
- )
+ underTest = recreateRepo()
}
@Test
@@ -400,6 +408,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
fun carrierId_initialValueCaptured() =
testScope.runTest {
whenever(telephonyManager.simCarrierId).thenReturn(1234)
+ underTest = recreateRepo()
var latest: Int? = null
val job = underTest.carrierId.onEach { latest = it }.launchIn(this)
@@ -430,6 +439,8 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
@Test
fun carrierNetworkChange() =
testScope.runTest {
+ underTest = recreateRepo()
+
var latest: Boolean? = null
val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
@@ -622,24 +633,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, true)
// Re-create the repository, because the flag is read at init
- underTest =
- MobileConnectionRepositoryImpl(
- SUB_1_ID,
- context,
- subscriptionModel,
- DEFAULT_NAME_MODEL,
- SEP,
- connectivityManager,
- telephonyManager,
- systemUiCarrierConfig,
- fakeBroadcastDispatcher,
- mobileMappings,
- testDispatcher,
- logger,
- tableLogger,
- flags,
- testScope.backgroundScope,
- )
+ underTest = recreateRepo()
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
@@ -671,24 +665,7 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
flags.set(ROAMING_INDICATOR_VIA_DISPLAY_INFO, false)
// Re-create the repository, because the flag is read at init
- underTest =
- MobileConnectionRepositoryImpl(
- SUB_1_ID,
- context,
- subscriptionModel,
- DEFAULT_NAME_MODEL,
- SEP,
- connectivityManager,
- telephonyManager,
- systemUiCarrierConfig,
- fakeBroadcastDispatcher,
- mobileMappings,
- testDispatcher,
- logger,
- tableLogger,
- flags,
- testScope.backgroundScope,
- )
+ underTest = recreateRepo()
var latest: Boolean? = null
val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
@@ -1441,14 +1418,14 @@ class MobileConnectionRepositoryTest : SysuiTestCase() {
}
companion object {
- private const val SUB_1_ID = 1
+ const val SUB_1_ID = 1
- private const val DEFAULT_NAME = "Fake Mobile Network"
- private val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
- private const val SEP = "-"
+ const val DEFAULT_NAME = "Fake Mobile Network"
+ val DEFAULT_NAME_MODEL = NetworkNameModel.Default(DEFAULT_NAME)
+ const val SEP = "-"
- private const val SPN = "testSpn"
- private const val DATA_SPN = "testDataSpn"
- private const val PLMN = "testPlmn"
+ const val SPN = "testSpn"
+ const val DATA_SPN = "testDataSpn"
+ const val PLMN = "testPlmn"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 6f21e795532b..caa6e21fd883 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -42,6 +42,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetwork
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig
import com.android.systemui.statusbar.pipeline.mobile.data.model.testCarrierConfig
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -92,47 +93,53 @@ import org.mockito.MockitoAnnotations
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@SmallTest
-class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
- private lateinit var underTest: MobileConnectionRepositoryImpl
+class MobileConnectionTelephonySmokeTests : MobileConnectionTelephonySmokeTestsBase() {
+ override fun recreateRepo(): MobileConnectionRepository =
+ MobileConnectionRepositoryImpl(
+ SUB_1_ID,
+ context,
+ subscriptionModel,
+ DEFAULT_NAME,
+ SEP,
+ connectivityManager,
+ telephonyManager,
+ systemUiCarrierConfig,
+ fakeBroadcastDispatcher,
+ mobileMappings,
+ testDispatcher,
+ logger,
+ tableLogger,
+ flags,
+ testScope.backgroundScope,
+ )
+}
+
+abstract class MobileConnectionTelephonySmokeTestsBase : SysuiTestCase() {
+ protected lateinit var underTest: MobileConnectionRepository
- private val flags =
+ protected val flags =
FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var telephonyManager: TelephonyManager
- @Mock private lateinit var logger: MobileInputLogger
- @Mock private lateinit var tableLogger: TableLogBuffer
- @Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
+ @Mock protected lateinit var connectivityManager: ConnectivityManager
+ @Mock protected lateinit var telephonyManager: TelephonyManager
+ @Mock protected lateinit var logger: MobileInputLogger
+ @Mock protected lateinit var tableLogger: TableLogBuffer
+ @Mock protected lateinit var subscriptionModel: StateFlow<SubscriptionModel?>
- private val mobileMappings = FakeMobileMappingsProxy()
- private val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
+ protected val mobileMappings = FakeMobileMappingsProxy()
+ protected val systemUiCarrierConfig = SystemUiCarrierConfig(SUB_1_ID, testCarrierConfig())
- private val testDispatcher = UnconfinedTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ protected val testDispatcher = UnconfinedTestDispatcher()
+ protected val testScope = TestScope(testDispatcher)
+
+ abstract fun recreateRepo(): MobileConnectionRepository
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
- underTest =
- MobileConnectionRepositoryImpl(
- SUB_1_ID,
- context,
- subscriptionModel,
- DEFAULT_NAME,
- SEP,
- connectivityManager,
- telephonyManager,
- systemUiCarrierConfig,
- fakeBroadcastDispatcher,
- mobileMappings,
- testDispatcher,
- logger,
- tableLogger,
- flags,
- testScope.backgroundScope,
- )
+ underTest = recreateRepo()
}
@Test
@@ -329,9 +336,9 @@ class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
}
companion object {
- private const val SUB_1_ID = 1
+ const val SUB_1_ID = 1
- private val DEFAULT_NAME = NetworkNameModel.Default("default name")
- private const val SEP = "-"
+ val DEFAULT_NAME = NetworkNameModel.Default("default name")
+ const val SEP = "-"
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt
new file mode 100644
index 000000000000..65849ae103c4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryKairosAdapterTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.kairos.ExperimentalKairosApi
+import com.android.systemui.kairos.launchKairosNetwork
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeCarrierConfigRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryKairosAdapter
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runCurrent
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+
+@OptIn(ExperimentalKairosApi::class, ExperimentalCoroutinesApi::class)
+@SmallTest
+// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
+// to run the callback and this makes the looper place nicely with TestScope etc.
+@TestableLooper.RunWithLooper
+class MobileConnectionsRepositoryKairosAdapterTest :
+ MobileConnectionsRepositoryTest<MobileConnectionsRepositoryKairosAdapter>() {
+
+ var job: Job? = null
+ val kairosNetwork = testScope.backgroundScope.launchKairosNetwork()
+
+ override fun recreateRepo(): MobileConnectionsRepositoryKairosAdapter {
+ val carrierConfigRepo = FakeCarrierConfigRepository()
+ lateinit var connectionsRepo: MobileConnectionsRepositoryKairosImpl
+ connectionsRepo =
+ MobileConnectionsRepositoryKairosImpl(
+ connectivityRepository = connectivityRepository,
+ subscriptionManager = subscriptionManager,
+ subscriptionManagerProxy = subscriptionManagerProxy,
+ telephonyManager = telephonyManager,
+ logger = logger,
+ tableLogger = summaryLogger,
+ mobileMappingsProxy = mobileMappings,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ context = context,
+ bgDispatcher = testDispatcher,
+ mainDispatcher = testDispatcher,
+ airplaneModeRepository = airplaneModeRepository,
+ wifiRepository = wifiRepository,
+ keyguardUpdateMonitor = updateMonitor,
+ dumpManager = mock(),
+ mobileRepoFactory = {
+ MobileConnectionRepositoryKairosFactoryImpl(
+ context = context,
+ connectionsRepo = connectionsRepo,
+ logFactory = logBufferFactory,
+ carrierConfigRepo = carrierConfigRepo,
+ telephonyManager = telephonyManager,
+ mobileRepoFactory = {
+ subId,
+ mobileLogger,
+ subscriptionModel,
+ defaultNetworkName,
+ networkNameSeparator,
+ systemUiCarrierConfig,
+ telephonyManager ->
+ MobileConnectionRepositoryKairosImpl(
+ subId = subId,
+ context = context,
+ subscriptionModel = subscriptionModel,
+ defaultNetworkName = defaultNetworkName,
+ networkNameSeparator = networkNameSeparator,
+ connectivityManager = connectivityManager,
+ telephonyManager = telephonyManager,
+ systemUiCarrierConfig = systemUiCarrierConfig,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ mobileMappingsProxy = mobileMappings,
+ bgDispatcher = testDispatcher,
+ logger = logger,
+ tableLogBuffer = mobileLogger,
+ flags = flags,
+ )
+ },
+ mergedRepoFactory =
+ CarrierMergedConnectionRepositoryKairos.Factory(
+ telephonyManager,
+ wifiRepository,
+ ),
+ )
+ },
+ )
+
+ val adapter =
+ MobileConnectionsRepositoryKairosAdapter(
+ kairosRepo = connectionsRepo,
+ kairosNetwork = kairosNetwork,
+ scope = testScope.backgroundScope,
+ connectivityRepository = connectivityRepository,
+ context = context,
+ carrierConfigRepo = carrierConfigRepo,
+ )
+
+ job?.cancel()
+ Mockito.clearInvocations(telephonyManager)
+ job =
+ testScope.backgroundScope.launch {
+ kairosNetwork.activateSpec {
+ connectionsRepo.run { activate() }
+ adapter.run { activate() }
+ }
+ }
+ testScope.runCurrent() // ensure everything is activated
+ return adapter
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index d1d6e27332b0..c3662880c5cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -37,6 +37,7 @@ import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.telephony.SubscriptionManager.PROFILE_CLASS_UNSET
import android.telephony.TelephonyCallback
import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
+import android.telephony.TelephonyCallback.EmergencyCallbackModeListener
import android.telephony.TelephonyManager
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -58,6 +59,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.CarrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.carrierConfigRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.tableBufferLogName
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -78,6 +80,7 @@ import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
+import java.time.Duration
import java.util.UUID
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
@@ -93,6 +96,7 @@ import org.junit.Test
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.atLeast
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
@@ -104,34 +108,311 @@ import org.mockito.kotlin.whenever
// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
// to run the callback and this makes the looper place nicely with TestScope etc.
@TestableLooper.RunWithLooper
-class MobileConnectionsRepositoryTest : SysuiTestCase() {
+class MobileConnectionsRepositoryImplTest :
+ MobileConnectionsRepositoryTest<MobileConnectionsRepositoryImpl>() {
+ override fun recreateRepo() =
+ MobileConnectionsRepositoryImpl(
+ connectivityRepository = connectivityRepository,
+ subscriptionManager = subscriptionManager,
+ subscriptionManagerProxy = subscriptionManagerProxy,
+ telephonyManager = telephonyManager,
+ logger = logger,
+ tableLogger = summaryLogger,
+ mobileMappingsProxy = mobileMappings,
+ broadcastDispatcher = fakeBroadcastDispatcher,
+ context = context,
+ bgDispatcher = testDispatcher,
+ scope = testScope.backgroundScope,
+ mainDispatcher = testDispatcher,
+ airplaneModeRepository = airplaneModeRepository,
+ wifiRepository = wifiRepository,
+ fullMobileRepoFactory = fullConnectionFactory,
+ keyguardUpdateMonitor = updateMonitor,
+ dumpManager = mock(),
+ )
+
+ @Test
+ fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
+ testScope.runTest {
+ val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+ collectLastValue(underTest.subscriptions)
+
+ // GIVEN active repo is updated before the subscription list updates
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ assertThat(activeRepo).isNotNull()
+
+ // GIVEN the subscription list is then updated which includes the active data sub id
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // WHEN requesting a connection repository for the subscription
+ val newRepo = underTest.getRepoForSubId(SUB_2_ID)
+
+ // THEN the newly request repo has been cached and reused
+ assertThat(activeRepo).isSameInstanceAs(newRepo)
+ }
+
+ @Test
+ fun testConnectionRepository_invalidSubId_doesNotThrow() =
+ testScope.runTest {
+ underTest.getRepoForSubId(SUB_1_ID)
+ // No exception
+ }
+
+ @Test
+ fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_CM))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+ val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+ assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+ assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+ }
+
+ @Test
+ fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_CM))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+ var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+ assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+ assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+ // WHEN the wifi network updates to be not carrier merged
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
+ runCurrent()
+
+ // THEN the repos update
+ val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+ mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+ assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+ assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+ }
+
+ @Test
+ fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
+ setWifiState(isCarrierMerged = false)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_CM))
+ getSubscriptionCallback().onSubscriptionsChanged()
+ runCurrent()
+
+ val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+ var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+ assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
+ assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+
+ // WHEN the wifi network updates to be carrier merged
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ runCurrent()
+
+ // THEN the repos update
+ val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
+ mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
+ assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
+ assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
+ }
+
+ @Test
+ @Ignore("b/333912012")
+ fun testConnectionCache_clearsInvalidSubscriptions() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+ // SUB_2 disappears
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+ }
+
+ @Test
+ @Ignore("b/333912012")
+ fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
+ setWifiState(isCarrierMerged = true)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+ val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
+
+ // SUB_2 and SUB_CM disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
+ }
+
+ /** Regression test for b/261706421 */
+ @Test
+ @Ignore("b/333912012")
+ fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
+ testScope.runTest {
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1, SUB_2))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Get repos to trigger caching
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+ val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+ assertThat(underTest.getSubIdRepoCache())
+ .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ assertThat(underTest.getSubIdRepoCache()).isEmpty()
+ }
+
+ @Test
+ fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
+ testScope.runTest {
+ var latestActiveRepo: MobileConnectionRepository? = null
+ collectLastValue(
+ underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
+ latestActiveRepo = underTest.getRepoForSubId(it)
+ }
+ )
+
+ val latestSubscriptions by collectLastValue(underTest.subscriptions)
+
+ // Active data subscription id is sent, but no subscription change has been posted yet
+ getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
+ .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+
+ // Subscriptions list is empty
+ assertThat(latestSubscriptions).isEmpty()
+ // getRepoForSubId does not throw
+ assertThat(latestActiveRepo).isNotNull()
+ }
+
+ @Test
+ fun testConnectionsCache_keepsReposCached() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Sub1 comes back
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1_1).isSameInstanceAs(repo1_2)
+ }
+
+ @Test
+ fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
+ testScope.runTest {
+ // Collect subscriptions to start the job
+ collectLastValue(underTest.subscriptions)
+
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_1))
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ // Client grabs a reference to a repository, but doesn't keep it around
+ underTest.getRepoForSubId(SUB_1_ID)
+
+ // All subscriptions disappear
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+ getSubscriptionCallback().onSubscriptionsChanged()
+
+ val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+
+ assertThat(repo1).isNotNull()
+ }
+}
+
+abstract class MobileConnectionsRepositoryTest<T : MobileConnectionsRepository> : SysuiTestCase() {
private val kosmos = testKosmos()
- private val flags =
+ protected val flags =
FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) }
private lateinit var connectionFactory: MobileConnectionRepositoryImpl.Factory
private lateinit var carrierMergedFactory: CarrierMergedConnectionRepository.Factory
- private lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
- private lateinit var connectivityRepository: ConnectivityRepository
- private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
- private lateinit var wifiRepository: WifiRepository
+ protected lateinit var fullConnectionFactory: FullMobileConnectionRepository.Factory
+ protected lateinit var connectivityRepository: ConnectivityRepository
+ protected lateinit var airplaneModeRepository: FakeAirplaneModeRepository
+ protected lateinit var wifiRepository: WifiRepository
private lateinit var carrierConfigRepository: CarrierConfigRepository
- @Mock private lateinit var connectivityManager: ConnectivityManager
- @Mock private lateinit var subscriptionManager: SubscriptionManager
- @Mock private lateinit var telephonyManager: TelephonyManager
- @Mock private lateinit var logger: MobileInputLogger
- private val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
- @Mock private lateinit var logBufferFactory: TableLogBufferFactory
- @Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
+ @Mock protected lateinit var connectivityManager: ConnectivityManager
+ @Mock protected lateinit var subscriptionManager: SubscriptionManager
+ @Mock protected lateinit var telephonyManager: TelephonyManager
+ @Mock protected lateinit var logger: MobileInputLogger
+ protected val summaryLogger = logcatTableLogBuffer(kosmos, "summaryLogger")
+ @Mock protected lateinit var logBufferFactory: TableLogBufferFactory
+ @Mock protected lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var wifiManager: WifiManager
@Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
@Mock private lateinit var wifiPickerTracker: WifiPickerTracker
private val wifiTableLogBuffer = logcatTableLogBuffer(kosmos, "wifiTableLog")
- private val mobileMappings = FakeMobileMappingsProxy()
- private val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
+ protected val mobileMappings = FakeMobileMappingsProxy()
+ protected val subscriptionManagerProxy = FakeSubscriptionManagerProxy()
private val mainExecutor = FakeExecutor(FakeSystemClock())
private val wifiLogBuffer = LogBuffer("wifi", maxSize = 100, logcatEchoTracker = mock())
private val wifiPickerTrackerCallback =
@@ -139,10 +420,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
private val vcnTransportInfo = VcnTransportInfo.Builder().build()
private val userRepository = kosmos.fakeUserRepository
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
+ protected val testDispatcher = StandardTestDispatcher()
+ protected val testScope = TestScope(testDispatcher)
- private lateinit var underTest: MobileConnectionsRepositoryImpl
+ protected lateinit var underTest: T
@Before
fun setUp() {
@@ -237,30 +518,13 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
carrierMergedRepoFactory = carrierMergedFactory,
)
- underTest =
- MobileConnectionsRepositoryImpl(
- connectivityRepository,
- subscriptionManager,
- subscriptionManagerProxy,
- telephonyManager,
- logger,
- summaryLogger,
- mobileMappings,
- fakeBroadcastDispatcher,
- context,
- /* bgDispatcher = */ testDispatcher,
- testScope.backgroundScope,
- /* mainDispatcher = */ testDispatcher,
- airplaneModeRepository,
- wifiRepository,
- fullConnectionFactory,
- updateMonitor,
- mock(),
- )
+ underTest = recreateRepo()
testScope.runCurrent()
}
+ abstract fun recreateRepo(): T
+
@Test
fun testSubscriptions_initiallyEmpty() =
testScope.runTest {
@@ -410,9 +674,17 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
fun activeRepo_updatesWithActiveDataId() =
testScope.runTest {
val latest by collectLastValue(underTest.activeMobileDataRepository)
+ runCurrent()
- getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
- .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallbacks().forEach { it.onSubscriptionsChanged() }
+ runCurrent()
+
+ getTelephonyCallbacksForType<ActiveDataSubscriptionIdListener>().forEach {
+ it.onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ }
+ runCurrent()
assertThat(latest?.subId).isEqualTo(SUB_2_ID)
}
@@ -422,6 +694,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.activeMobileDataRepository)
+ whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+ .thenReturn(listOf(SUB_2))
+ getSubscriptionCallbacks().forEach { it.onSubscriptionsChanged() }
+
getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
.onActiveDataSubscriptionIdChanged(SUB_2_ID)
@@ -437,60 +713,15 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
/** Regression test for b/268146648. */
fun activeSubIdIsSetBeforeSubscriptionsAreUpdated_doesNotThrow() =
testScope.runTest {
- val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
+ val activeRepo = collectLastValue(underTest.activeMobileDataRepository)
val subscriptions by collectLastValue(underTest.subscriptions)
- getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
- .onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ getTelephonyCallbacksForType<ActiveDataSubscriptionIdListener>().forEach {
+ it.onActiveDataSubscriptionIdChanged(SUB_2_ID)
+ }
assertThat(subscriptions).isEmpty()
- assertThat(activeRepo).isNotNull()
- }
-
- @Test
- fun getRepoForSubId_activeDataSubIdIsRequestedBeforeSubscriptionsUpdate() =
- testScope.runTest {
- var latestActiveRepo: MobileConnectionRepository? = null
- collectLastValue(
- underTest.activeMobileDataSubscriptionId.filterNotNull().onEach {
- latestActiveRepo = underTest.getRepoForSubId(it)
- }
- )
-
- val latestSubscriptions by collectLastValue(underTest.subscriptions)
-
- // Active data subscription id is sent, but no subscription change has been posted yet
- getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
- .onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
- // Subscriptions list is empty
- assertThat(latestSubscriptions).isEmpty()
- // getRepoForSubId does not throw
- assertThat(latestActiveRepo).isNotNull()
- }
-
- @Test
- fun activeDataSentBeforeSubscriptionList_subscriptionReusesActiveDataRepo() =
- testScope.runTest {
- val activeRepo by collectLastValue(underTest.activeMobileDataRepository)
- collectLastValue(underTest.subscriptions)
-
- // GIVEN active repo is updated before the subscription list updates
- getTelephonyCallbackForType<ActiveDataSubscriptionIdListener>()
- .onActiveDataSubscriptionIdChanged(SUB_2_ID)
-
- assertThat(activeRepo).isNotNull()
-
- // GIVEN the subscription list is then updated which includes the active data sub id
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // WHEN requesting a connection repository for the subscription
- val newRepo = underTest.getRepoForSubId(SUB_2_ID)
-
- // THEN the newly request repo has been cached and reused
- assertThat(activeRepo).isSameInstanceAs(newRepo)
+ activeRepo.invoke() // does not throw
}
@Test
@@ -501,6 +732,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1))
getSubscriptionCallback().onSubscriptionsChanged()
+ runCurrent()
val repo1 = underTest.getRepoForSubId(SUB_1_ID)
val repo2 = underTest.getRepoForSubId(SUB_1_ID)
@@ -525,80 +757,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(repo1).isSameInstanceAs(repo2)
}
- @Test
- fun testConnectionRepository_carrierMergedAndMobileSubs_usesCorrectRepos() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- setWifiState(isCarrierMerged = true)
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_CM))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
- val mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
- assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
- assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
- }
-
- @Test
- fun testSubscriptions_subNoLongerCarrierMerged_repoUpdates() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- setWifiState(isCarrierMerged = true)
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_CM))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
- var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
- assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
- assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
- // WHEN the wifi network updates to be not carrier merged
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- setWifiState(isCarrierMerged = false)
- runCurrent()
-
- // THEN the repos update
- val noLongerCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
- mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
- assertThat(noLongerCarrierMergedRepo.getIsCarrierMerged()).isFalse()
- assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
- }
-
- @Test
- fun testSubscriptions_subBecomesCarrierMerged_repoUpdates() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_ACTIVE)
- setWifiState(isCarrierMerged = false)
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_CM))
- getSubscriptionCallback().onSubscriptionsChanged()
- runCurrent()
-
- val notYetCarrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
- var mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
- assertThat(notYetCarrierMergedRepo.getIsCarrierMerged()).isFalse()
- assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
-
- // WHEN the wifi network updates to be carrier merged
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- setWifiState(isCarrierMerged = true)
- runCurrent()
-
- // THEN the repos update
- val carrierMergedRepo = underTest.getRepoForSubId(SUB_CM_ID)
- mobileRepo = underTest.getRepoForSubId(SUB_1_ID)
- assertThat(carrierMergedRepo.getIsCarrierMerged()).isTrue()
- assertThat(mobileRepo.getIsCarrierMerged()).isFalse()
- }
-
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Test
fun testDeviceEmergencyCallState_eagerlyChecksState() =
@@ -674,139 +832,6 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
}
@Test
- @Ignore("b/333912012")
- fun testConnectionCache_clearsInvalidSubscriptions() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // Get repos to trigger caching
- val repo1 = underTest.getRepoForSubId(SUB_1_ID)
- val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-
- assertThat(underTest.getSubIdRepoCache())
- .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
-
- // SUB_2 disappears
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
- }
-
- @Test
- @Ignore("b/333912012")
- fun testConnectionCache_clearsInvalidSubscriptions_includingCarrierMerged() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, WIFI_NETWORK_CAPS_CM)
- setWifiState(isCarrierMerged = true)
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2, SUB_CM))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // Get repos to trigger caching
- val repo1 = underTest.getRepoForSubId(SUB_1_ID)
- val repo2 = underTest.getRepoForSubId(SUB_2_ID)
- val repoCarrierMerged = underTest.getRepoForSubId(SUB_CM_ID)
-
- assertThat(underTest.getSubIdRepoCache())
- .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2, SUB_CM_ID, repoCarrierMerged)
-
- // SUB_2 and SUB_CM disappear
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(underTest.getSubIdRepoCache()).containsExactly(SUB_1_ID, repo1)
- }
-
- /** Regression test for b/261706421 */
- @Test
- @Ignore("b/333912012")
- fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
- testScope.runTest {
- collectLastValue(underTest.subscriptions)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1, SUB_2))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // Get repos to trigger caching
- val repo1 = underTest.getRepoForSubId(SUB_1_ID)
- val repo2 = underTest.getRepoForSubId(SUB_2_ID)
-
- assertThat(underTest.getSubIdRepoCache())
- .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
-
- // All subscriptions disappear
- whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
- getSubscriptionCallback().onSubscriptionsChanged()
-
- assertThat(underTest.getSubIdRepoCache()).isEmpty()
- }
-
- @Test
- fun testConnectionsCache_keepsReposCached() =
- testScope.runTest {
- // Collect subscriptions to start the job
- collectLastValue(underTest.subscriptions)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- val repo1_1 = underTest.getRepoForSubId(SUB_1_ID)
-
- // All subscriptions disappear
- whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // Sub1 comes back
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- val repo1_2 = underTest.getRepoForSubId(SUB_1_ID)
-
- assertThat(repo1_1).isSameInstanceAs(repo1_2)
- }
-
- @Test
- fun testConnectionsCache_doesNotDropReferencesThatHaveBeenRealized() =
- testScope.runTest {
- // Collect subscriptions to start the job
- collectLastValue(underTest.subscriptions)
-
- whenever(subscriptionManager.completeActiveSubscriptionInfoList)
- .thenReturn(listOf(SUB_1))
- getSubscriptionCallback().onSubscriptionsChanged()
-
- // Client grabs a reference to a repository, but doesn't keep it around
- underTest.getRepoForSubId(SUB_1_ID)
-
- // All subscriptions disappear
- whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
- getSubscriptionCallback().onSubscriptionsChanged()
-
- val repo1 = underTest.getRepoForSubId(SUB_1_ID)
-
- assertThat(repo1).isNotNull()
- }
-
- @Test
- fun testConnectionRepository_invalidSubId_doesNotThrow() =
- testScope.runTest {
- underTest.getRepoForSubId(SUB_1_ID)
- // No exception
- }
-
- @Test
fun connectionRepository_logBufferContainsSubIdInItsName() =
testScope.runTest {
collectLastValue(underTest.subscriptions)
@@ -814,6 +839,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(subscriptionManager.completeActiveSubscriptionInfoList)
.thenReturn(listOf(SUB_1, SUB_2))
getSubscriptionCallback().onSubscriptionsChanged()
+ runCurrent()
// Get repos to trigger creation
underTest.getRepoForSubId(SUB_1_ID)
@@ -848,15 +874,27 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
fun defaultDataSubId_fetchesInitialValueOnStart() =
testScope.runTest {
subscriptionManagerProxy.defaultDataSubId = 2
+ underTest = recreateRepo()
+
val latest by collectLastValue(underTest.defaultDataSubId)
assertThat(latest).isEqualTo(2)
}
+ private fun setDefaultDataSubId(subId: Int) {
+ subscriptionManagerProxy.defaultDataSubId = subId
+ fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED).apply {
+ putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId)
+ },
+ )
+ }
+
@Test
fun defaultDataSubId_filtersOutInvalidSubIds() =
testScope.runTest {
- subscriptionManagerProxy.defaultDataSubId = INVALID_SUBSCRIPTION_ID
+ setDefaultDataSubId(INVALID_SUBSCRIPTION_ID)
val latest by collectLastValue(underTest.defaultDataSubId)
assertThat(latest).isNull()
@@ -865,15 +903,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun defaultDataSubId_filtersOutInvalidSubIds_fromValidToInvalid() =
testScope.runTest {
- subscriptionManagerProxy.defaultDataSubId = 2
+ setDefaultDataSubId(2)
val latest by collectLastValue(underTest.defaultDataSubId)
assertThat(latest).isEqualTo(2)
- val intent =
- Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
- .putExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(context, intent)
+ setDefaultDataSubId(INVALID_SUBSCRIPTION_ID)
assertThat(latest).isNull()
}
@@ -881,7 +916,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
@Test
fun defaultDataSubId_fetchesCurrentOnRestart() =
testScope.runTest {
- subscriptionManagerProxy.defaultDataSubId = 2
+ setDefaultDataSubId(2)
var latest: Int? = null
var job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
runCurrent()
@@ -894,7 +929,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
latest = null
- subscriptionManagerProxy.defaultDataSubId = 1
+ setDefaultDataSubId(1)
job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
runCurrent()
@@ -1281,26 +1316,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
// The initial value will be fetched when the repo is created, so we need to override
// the resources and then re-create the repo.
- underTest =
- MobileConnectionsRepositoryImpl(
- connectivityRepository,
- subscriptionManager,
- subscriptionManagerProxy,
- telephonyManager,
- logger,
- summaryLogger,
- mobileMappings,
- fakeBroadcastDispatcher,
- context,
- testDispatcher,
- testScope.backgroundScope,
- testDispatcher,
- airplaneModeRepository,
- wifiRepository,
- fullConnectionFactory,
- updateMonitor,
- mock(),
- )
+ underTest = recreateRepo()
val latest by collectLastValue(underTest.defaultDataSubRatConfig)
@@ -1428,6 +1444,12 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
assertThat(underTest.getIsAnySimSecure()).isFalse()
whenever(updateMonitor.isSimPinSecure).thenReturn(true)
+ org.mockito.kotlin
+ .argumentCaptor<KeyguardUpdateMonitorCallback>()
+ .apply { verify(updateMonitor, atLeast(0)).registerCallback(capture()) }
+ .allValues
+ .forEach { it.onSimStateChanged(0, 0, 0) }
+ runCurrent()
assertThat(underTest.getIsAnySimSecure()).isTrue()
}
@@ -1447,19 +1469,26 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
testScope.runTest {
whenever(telephonyManager.emergencyCallbackMode).thenReturn(true)
+ getTelephonyCallbacksForType<EmergencyCallbackModeListener>().forEach {
+ it.onCallbackModeStarted(
+ TelephonyManager.EMERGENCY_CALLBACK_MODE_SMS,
+ Duration.ZERO,
+ 0,
+ )
+ }
runCurrent()
assertThat(underTest.isInEcmMode()).isTrue()
}
- private fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+ protected fun TestScope.getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
runCurrent()
val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
return callbackCaptor.value!!
}
- private fun setWifiState(isCarrierMerged: Boolean) {
+ protected fun setWifiState(isCarrierMerged: Boolean) {
if (isCarrierMerged) {
val mergedEntry =
mock<MergedCarrierEntry>().apply {
@@ -1481,7 +1510,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
wifiPickerTrackerCallback.value.onWifiEntriesChanged()
}
- private fun TestScope.getSubscriptionCallback():
+ protected fun TestScope.getSubscriptionCallback():
SubscriptionManager.OnSubscriptionsChangedListener {
runCurrent()
val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
@@ -1490,25 +1519,39 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
return callbackCaptor.value!!
}
- private fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
+ protected fun TestScope.getSubscriptionCallbacks():
+ List<SubscriptionManager.OnSubscriptionsChangedListener> {
+ runCurrent()
+ val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+ verify(subscriptionManager, atLeast(0))
+ .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+ return callbackCaptor.allValues
+ }
+
+ fun TestScope.getTelephonyCallbacks(): List<TelephonyCallback> {
runCurrent()
val callbackCaptor = argumentCaptor<TelephonyCallback>()
- verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
+ verify(telephonyManager, atLeast(0))
+ .registerTelephonyCallback(any(), callbackCaptor.capture())
return callbackCaptor.allValues
}
- private inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
- val cbs = this.getTelephonyCallbacks().filterIsInstance<T>()
+ inline fun <reified T> TestScope.getTelephonyCallbackForType(): T {
+ val cbs = getTelephonyCallbacksForType<T>()
assertThat(cbs.size).isEqualTo(1)
return cbs[0]
}
+ inline fun <reified T> TestScope.getTelephonyCallbacksForType(): List<T> {
+ return getTelephonyCallbacks().filterIsInstance<T>()
+ }
+
companion object {
// Subscription 1
- private const val SUB_1_ID = 1
+ const val SUB_1_ID = 1
private const val SUB_1_NAME = "Carrier $SUB_1_ID"
private val GROUP_1 = ParcelUuid(UUID.randomUUID())
- private val SUB_1 =
+ val SUB_1 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_1_ID)
whenever(it.groupUuid).thenReturn(GROUP_1)
@@ -1524,10 +1567,10 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
)
// Subscription 2
- private const val SUB_2_ID = 2
+ const val SUB_2_ID = 2
private const val SUB_2_NAME = "Carrier $SUB_2_ID"
private val GROUP_2 = ParcelUuid(UUID.randomUUID())
- private val SUB_2 =
+ val SUB_2 =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_2_ID)
whenever(it.groupUuid).thenReturn(GROUP_2)
@@ -1552,6 +1595,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(it.subscriptionId).thenReturn(SUB_3_ID_GROUPED)
whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ whenever(it.carrierName).thenReturn("")
}
// Subscription 4
@@ -1561,17 +1605,18 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(it.subscriptionId).thenReturn(SUB_4_ID_GROUPED)
whenever(it.groupUuid).thenReturn(GROUP_ID_3_4)
whenever(it.profileClass).thenReturn(PROFILE_CLASS_UNSET)
+ whenever(it.carrierName).thenReturn("")
}
// Subs 3 and 4 are considered to be in the same group ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
private const val NET_ID = 123
- private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
+ val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
// Carrier merged subscription
- private const val SUB_CM_ID = 5
+ const val SUB_CM_ID = 5
private const val SUB_CM_NAME = "Carrier $SUB_CM_ID"
- private val SUB_CM =
+ val SUB_CM =
mock<SubscriptionInfo>().also {
whenever(it.subscriptionId).thenReturn(SUB_CM_ID)
whenever(it.carrierName).thenReturn(SUB_CM_NAME)
@@ -1590,7 +1635,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(this.isCarrierMerged).thenReturn(true)
whenever(this.subscriptionId).thenReturn(SUB_CM_ID)
}
- private val WIFI_NETWORK_CAPS_CM =
+ val WIFI_NETWORK_CAPS_CM =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(WIFI_INFO_CM)
@@ -1602,7 +1647,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() {
whenever(this.isPrimary).thenReturn(true)
whenever(this.isCarrierMerged).thenReturn(false)
}
- private val WIFI_NETWORK_CAPS_ACTIVE =
+ val WIFI_NETWORK_CAPS_ACTIVE =
mock<NetworkCapabilities>().also {
whenever(it.hasTransport(TRANSPORT_WIFI)).thenReturn(true)
whenever(it.transportInfo).thenReturn(WIFI_INFO_ACTIVE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
index 3247a1ab6eb0..8ff8fe6cc3d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java
@@ -44,6 +44,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.view.RotationPolicy;
+import com.android.settingslib.devicestate.AndroidSecureSettings;
import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
@@ -117,7 +118,8 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
mContentResolver = mContext.getContentResolver();
- mSettingsManager = DeviceStateRotationLockSettingsManager.getInstance(mContext);
+ mSettingsManager = new DeviceStateRotationLockSettingsManager(mContext,
+ new AndroidSecureSettings(mContentResolver));
mDeviceStateRotationLockSettingController =
new DeviceStateRotationLockSettingController(
mFakeRotationPolicy,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 54df9e99baa5..bb6ba46f1a0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -25,13 +25,12 @@ import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.util.concurrency.DelayableExecutor
@@ -78,7 +77,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
@Mock
private lateinit var dumpManager: DumpManager
@Mock
- private lateinit var windowManager: ViewCaptureAwareWindowManager
+ private lateinit var windowManager: WindowManager
@Mock
private lateinit var powerManager: PowerManager
@@ -1143,7 +1142,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
inner class TestController(
context: Context,
logger: TemporaryViewLogger<ViewInfo>,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
@Main mainExecutor: DelayableExecutor,
accessibilityManager: AccessibilityManager,
configurationController: ConfigurationController,
@@ -1155,7 +1154,7 @@ class TemporaryViewDisplayControllerTest : SysuiTestCase() {
) : TemporaryViewDisplayController<ViewInfo, TemporaryViewLogger<ViewInfo>>(
context,
logger,
- viewCaptureAwareWindowManager,
+ windowManager,
mainExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 4260b6558950..664f2df62782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -30,8 +30,6 @@ import android.widget.TextView
import androidx.core.animation.doOnCancel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.app.viewcapture.ViewCapture
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
@@ -86,7 +84,6 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var viewUtil: ViewUtil
@Mock private lateinit var vibratorHelper: VibratorHelper
@Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
- @Mock private lateinit var lazyViewCapture: Lazy<ViewCapture>
private lateinit var chipbarAnimator: TestChipbarAnimator
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
private lateinit var fakeWakeLock: WakeLockFake
@@ -115,8 +112,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
ChipbarCoordinator(
context,
logger,
- ViewCaptureAwareWindowManager(windowManager, lazyViewCapture,
- isViewCaptureEnabled = false),
+ windowManager,
fakeExecutor,
accessibilityManager,
configurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/WindowManagerProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/utils/WindowManagerProviderImplTest.kt
new file mode 100644
index 000000000000..7b52237f0a01
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/WindowManagerProviderImplTest.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.utils
+
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.utils.windowmanager.WindowManagerProviderImpl
+import com.google.common.truth.Truth.assertThat
+import org.junit.runner.RunWith
+import org.junit.Test
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class WindowManagerProviderImplTest : SysuiTestCase() {
+
+ private val windowManagerProvider = WindowManagerProviderImpl()
+ private val windowManagerFromSystemService = mContext.getSystemService(WindowManager::class.java)
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING)
+ fun viewCaptureTracingEnabled_verifyWMInstanceDoesNotMatchContextOne() {
+ val windowManagerFromProvider = windowManagerProvider.getWindowManager(mContext)
+ assertThat(windowManagerFromProvider).isNotEqualTo(windowManagerFromSystemService)
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_ENABLE_VIEW_CAPTURE_TRACING)
+ fun viewCaptureTracingDisabled_verifyWMInstanceMatchesContextOne() {
+ mContext.addMockSystemService(WindowManager::class.java, windowManagerFromSystemService)
+
+ val windowManagerFromProvider = windowManagerProvider.getWindowManager(mContext)
+ assertThat(windowManagerFromProvider).isEqualTo(windowManagerFromSystemService)
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 0d7ce5353cd4..f89571f2db2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -101,8 +101,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.app.viewcapture.ViewCapture;
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.ProtoLog;
@@ -358,8 +356,6 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private Display mDefaultDisplay;
@Mock
- private Lazy<ViewCapture> mLazyViewCapture;
- @Mock
private SyncTransactionQueue mSyncQueue;
private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
@@ -429,8 +425,7 @@ public class BubblesTest extends SysuiTestCase {
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(
mContext,
new FakeWindowRootViewComponent.Factory(mNotificationShadeWindowView),
- new ViewCaptureAwareWindowManager(mWindowManager, mLazyViewCapture,
- /* isViewCaptureEnabled= */ false),
+ mWindowManager,
mActivityManager,
mDozeParameters,
mStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 846db6389d0c..2facc1c01ae1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -283,6 +283,9 @@ public abstract class SysuiTestCase {
}
public FakeBroadcastDispatcher getFakeBroadcastDispatcher() {
+ if (mSysuiDependency == null) {
+ return null;
+ }
return mSysuiDependency.getFakeBroadcastDispatcher();
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
index d3dccb021ff8..c86ba6ccf47f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCaseExt.kt
@@ -18,9 +18,22 @@ package com.android.systemui
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testCase
+import com.android.systemui.kosmos.useStandardTestDispatcher
fun SysuiTestCase.testKosmos(): Kosmos = Kosmos().apply { testCase = this@testKosmos }
+/**
+ * This should not be called directly. Instead, you can use:
+ * - testKosmos() to use the default dispatcher (which will soon be unconfined, see go/thetiger)
+ * - testKosmos().useStandardTestDispatcher() to explicitly choose the standard dispatcher
+ * - testKosmos().useUnconfinedTestDispatcher() to explicitly choose the unconfined dispatcher
+ *
+ * For details, see go/thetiger
+ */
+@Deprecated("Do not call this directly. Use testKosmos() with dispatcher functions if needed.")
+fun SysuiTestCase.testKosmosLegacy(): Kosmos =
+ Kosmos().useStandardTestDispatcher().apply { testCase = this@testKosmosLegacy }
+
/** Run [f] on the main thread and return its result once completed. */
fun <T : Any> SysuiTestCase.runOnMainThreadAndWaitForIdleSync(f: () -> T): T {
lateinit var result: T
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
index 511bede7349b..41dddce77a30 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt
@@ -33,6 +33,7 @@ var Kosmos.fromLockscreenTransitionInteractor by
transitionInteractor = keyguardTransitionInteractor,
internalTransitionInteractor = internalKeyguardTransitionInteractor,
scope = applicationCoroutineScope,
+ applicationScope = applicationCoroutineScope,
bgDispatcher = testDispatcher,
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
index 2f3d3c3e0489..ba1c598a79d7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/ui/viewmodel/MediaControlViewModelKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.media.controls.ui.viewmodel
import android.content.applicationContext
+import com.android.internal.logging.InstanceId
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
@@ -31,5 +32,9 @@ val Kosmos.mediaControlViewModel by
backgroundExecutor = fakeExecutor,
interactor = mediaControlInteractor,
logger = mediaUiEventLogger,
+ instanceId = InstanceId.fakeInstanceId(-1),
+ onAdded = {},
+ onRemoved = {},
+ onUpdated = {},
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 7a9b052481cb..349e670a9af3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -46,7 +46,6 @@ import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.session.shared.shadeSessionStorage
import com.android.systemui.scene.shared.logger.sceneLogger
-import com.android.systemui.settings.displayTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
@@ -65,7 +64,6 @@ val Kosmos.sceneContainerStartable by Fixture {
bouncerInteractor = bouncerInteractor,
keyguardInteractor = keyguardInteractor,
sysUiState = sysUiState,
- displayId = displayTracker.defaultDisplayId,
sceneLogger = sceneLogger,
falsingCollector = falsingCollector,
falsingManager = falsingManager,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
index aae32cfaafa6..f83fcb32aafe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/PrivacyDotWindowControllerStoreKosmos.kt
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.data.repository
-import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
import com.android.systemui.kosmos.Kosmos
@@ -35,14 +33,6 @@ val Kosmos.privacyDotWindowControllerStoreImpl by
windowControllerFactory = { _, _, _, _ -> mock() },
displayWindowPropertiesRepository = displayWindowPropertiesRepository,
privacyDotViewControllerStore = privacyDotViewControllerStore,
- viewCaptureAwareWindowManagerFactory =
- object : ViewCaptureAwareWindowManager.Factory {
- override fun create(
- windowManager: WindowManager
- ): ViewCaptureAwareWindowManager {
- return mock()
- }
- },
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
index c73838708a7a..da4027e46783 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/events/PrivacyDotWindowControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.events
import android.content.testableContext
+import android.view.fakeWindowManager
import android.view.layoutInflater
-import com.android.app.viewcapture.realCaptureAwareWindowManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.decor.privacyDotDecorProviderFactory
import com.android.systemui.kosmos.Kosmos
@@ -28,7 +28,7 @@ var Kosmos.privacyDotWindowController by
PrivacyDotWindowController(
testableContext.displayId,
privacyDotViewController,
- realCaptureAwareWindowManager,
+ fakeWindowManager,
layoutInflater,
fakeExecutor,
privacyDotDecorProviderFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 165f85d2cbba..4af4e804ff10 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.data.model
import android.app.PendingIntent
import android.graphics.drawable.Icon
import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.notification.stack.BUCKET_UNKNOWN
@@ -49,7 +49,7 @@ fun activeNotificationModel(
contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
callType: CallType = CallType.None,
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
) =
ActiveNotificationModel(
key = key,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
index 99323dbd7cce..ebe20af51c19 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt
@@ -22,6 +22,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shared.notifications.domain.interactor.notificationSettingsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
+import com.android.systemui.window.domain.interactor.windowRootViewBlurInteractor
val Kosmos.footerViewModel by Fixture {
FooterViewModel(
@@ -29,6 +30,7 @@ val Kosmos.footerViewModel by Fixture {
notificationSettingsInteractor = notificationSettingsInteractor,
seenNotificationsInteractor = seenNotificationsInteractor,
shadeInteractor = shadeInteractor,
+ windowRootViewBlurInteractor = windowRootViewBlurInteractor,
)
}
val Kosmos.footerViewModelFactory: FooterViewModel.Factory by Fixture {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
index 8fdf5dbf2aeb..aaa86aaaedc6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/FakePromotedNotificationContentExtractor.kt
@@ -17,21 +17,23 @@
package com.android.systemui.statusbar.notification.promoted
import android.app.Notification
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.RedactionType
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider
import org.junit.Assert
class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtractor {
@JvmField
- val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModel?>()
+ val contentForEntry = mutableMapOf<NotificationEntry, PromotedNotificationContentModels?>()
@JvmField val extractCalls = mutableListOf<Pair<NotificationEntry, Notification.Builder>>()
override fun extractContent(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
+ @RedactionType redactionType: Int,
imageModelProvider: ImageModelProvider,
- ): PromotedNotificationContentModel? {
+ ): PromotedNotificationContentModels? {
extractCalls.add(entry to recoveredBuilder)
if (contentForEntry.isEmpty()) {
@@ -44,7 +46,7 @@ class FakePromotedNotificationContentExtractor : PromotedNotificationContentExtr
}
}
- fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModel?) {
+ fun resetForEntry(entry: NotificationEntry, content: PromotedNotificationContentModels?) {
contentForEntry.clear()
contentForEntry[entry] = content
extractCalls.clear()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index 2b3158da38f9..c4542c4e709b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.promoted
import android.app.Notification
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform
@@ -39,9 +40,10 @@ fun Kosmos.setPromotedContent(entry: NotificationEntry) {
promotedNotificationContentExtractor.extractContent(
entry,
Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification),
+ REDACTION_TYPE_NONE,
RowImageInflater.newInstance(previousIndex = null, reinflating = false)
.useForContentModel(),
)
- entry.promotedNotificationContentModel =
+ entry.promotedNotificationContentModels =
requireNotNull(extractedContent) { "extractContent returned null" }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt
new file mode 100644
index 000000000000..6916d560a7ad
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentBuilder.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.statusbar.notification.promoted.shared.model
+
+class PromotedNotificationContentBuilder(val key: String) {
+ private val sharedBuilder = PromotedNotificationContentModel.Builder(key)
+
+ fun applyToShared(
+ block: PromotedNotificationContentModel.Builder.() -> Unit
+ ): PromotedNotificationContentBuilder {
+ sharedBuilder.apply(block)
+ return this
+ }
+
+ fun build(): PromotedNotificationContentModels {
+ val sharedModel = sharedBuilder.build()
+ return PromotedNotificationContentModels(sharedModel, sharedModel)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
index 3e96fd7c729f..e5e1a830231e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallTestHelper.kt
@@ -26,7 +26,7 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.addNotif
import com.android.systemui.statusbar.notification.data.repository.removeNotif
-import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModels
import com.android.systemui.statusbar.notification.shared.CallType
import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -39,7 +39,7 @@ fun inCallModel(
intent: PendingIntent? = null,
notificationKey: String = "test",
appName: String = "",
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
isAppVisible: Boolean = false,
) =
OngoingCallModel.InCall(
@@ -77,7 +77,7 @@ object OngoingCallTestHelper {
key: String = "notif",
startTimeMs: Long = 1000L,
statusBarChipIconView: StatusBarIconView? = createStatusBarIconViewOrNull(),
- promotedContent: PromotedNotificationContentModel? = null,
+ promotedContent: PromotedNotificationContentModels? = null,
contentIntent: PendingIntent? = null,
uid: Int = DEFAULT_UID,
appName: String = "Fake name",
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
index 3a19547f0713..f20fb3bf1779 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/FakeStatusBarWindowControllerFactory.kt
@@ -17,14 +17,14 @@
package com.android.systemui.statusbar.window
import android.content.Context
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
+import android.view.WindowManager
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController
import com.android.systemui.statusbar.layout.StatusBarContentInsetsProvider
class FakeStatusBarWindowControllerFactory : StatusBarWindowController.Factory {
override fun create(
context: Context,
- viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager,
+ windowManager: WindowManager,
statusBarConfigurationController: StatusBarConfigurationController,
contentInsetsProvider: StatusBarContentInsetsProvider,
) = FakeStatusBarWindowController()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
index f595aef41e2d..b0214769362f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerKosmos.kt
@@ -17,8 +17,8 @@
package com.android.systemui.statusbar.window
import android.content.testableContext
+import android.view.fakeWindowManager
import android.view.windowManagerService
-import com.android.app.viewcapture.realCaptureAwareWindowManager
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.fragments.fragmentService
import com.android.systemui.kosmos.Kosmos
@@ -33,7 +33,7 @@ val Kosmos.statusBarWindowControllerImpl by
StatusBarWindowControllerImpl(
testableContext,
statusBarWindowViewInflater,
- realCaptureAwareWindowManager,
+ fakeWindowManager,
statusBarConfigurationController,
windowManagerService,
statusBarContentInsetsProvider,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
index 4941ceb7991d..0f9310376b2a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStoreKosmos.kt
@@ -16,9 +16,6 @@
package com.android.systemui.statusbar.window
-import android.view.WindowManager
-import com.android.app.viewcapture.ViewCaptureAwareWindowManager
-import com.android.app.viewcapture.realCaptureAwareWindowManager
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayWindowPropertiesRepository
import com.android.systemui.kosmos.Kosmos
@@ -33,14 +30,6 @@ val Kosmos.multiDisplayStatusBarWindowControllerStore by
backgroundApplicationScope = applicationCoroutineScope,
controllerFactory = { _, _, _, _ -> mock() },
displayWindowPropertiesRepository = displayWindowPropertiesRepository,
- viewCaptureAwareWindowManagerFactory =
- object : ViewCaptureAwareWindowManager.Factory {
- override fun create(
- windowManager: WindowManager
- ): ViewCaptureAwareWindowManager {
- return realCaptureAwareWindowManager
- }
- },
statusBarConfigurationControllerStore = statusBarConfigurationControllerStore,
statusBarContentInsetsProviderStore = statusBarContentInsetsProviderStore,
displayRepository = displayRepository,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
index 1504df4ef6d0..6767300a22bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorKosmos.kt
@@ -55,5 +55,6 @@ val Kosmos.userSwitcherInteractor by
uiEventLogger = uiEventLogger,
userRestrictionChecker = userRestrictionChecker,
processWrapper = processWrapper,
+ userLogoutInteractor = userLogoutInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/windowmanager/FakeWindowManagerProvider.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/windowmanager/FakeWindowManagerProvider.kt
new file mode 100644
index 000000000000..5c8eae3183c7
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/windowmanager/FakeWindowManagerProvider.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.utils.windowmanager
+
+import android.content.Context
+import android.view.WindowManager
+
+/** Fake implementation of [WindowManagerProvider], to be used in tests only. */
+class FakeWindowManagerProvider(private val windowManager: WindowManager) : WindowManagerProvider {
+
+ override fun getWindowManager(context: Context): WindowManager {
+ return windowManager
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCsdWarningInteractorKosmos.kt
index 021c7bbb44cd..fc7fc0f4e5b4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/app/viewcapture/ViewCaptureAwareWindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCsdWarningInteractorKosmos.kt
@@ -14,23 +14,13 @@
* limitations under the License.
*/
-package com.android.app.viewcapture
+package com.android.systemui.volume.dialog.domain.interactor
-import android.view.fakeWindowManager
import com.android.systemui.kosmos.Kosmos
-import org.mockito.kotlin.mock
+import com.android.systemui.volume.dialog.shared.model.CsdWarningConfigModel
-val Kosmos.mockViewCaptureAwareWindowManager by
- Kosmos.Fixture { mock<ViewCaptureAwareWindowManager>() }
+val Kosmos.volumeDialogCsdWarningInteractor: VolumeDialogCsdWarningInteractor by
+ Kosmos.Fixture { VolumeDialogCsdWarningInteractor(volumeDialogStateInteractor) }
-val Kosmos.realCaptureAwareWindowManager by
- Kosmos.Fixture {
- ViewCaptureAwareWindowManager(
- fakeWindowManager,
- lazyViewCapture = lazy { mock<ViewCapture>() },
- isViewCaptureEnabled = false,
- )
- }
-
-var Kosmos.viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager by
- Kosmos.Fixture { mockViewCaptureAwareWindowManager }
+val Kosmos.csdWarningConfigModel: CsdWarningConfigModel by
+ Kosmos.Fixture { CsdWarningConfigModel(emptyList()) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelKosmos.kt
new file mode 100644
index 000000000000..5a58599eaade
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogPluginViewModelKosmos.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.volume.dialog.ui.viewmodel
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.volume.dialog.domain.interactor.csdWarningConfigModel
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogCsdWarningInteractor
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogSafetyWarningInteractor
+import com.android.systemui.volume.dialog.domain.interactor.volumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.volumeDialogLogger
+import com.android.systemui.volume.dialog.volumeDialog
+
+val Kosmos.volumeDialogPluginViewModel: VolumeDialogPluginViewModel by
+ Kosmos.Fixture {
+ VolumeDialogPluginViewModel(
+ backgroundScope,
+ volumeDialogVisibilityInteractor,
+ volumeDialogSafetyWarningInteractor,
+ volumeDialogCsdWarningInteractor,
+ { volumeDialog },
+ volumeDialogLogger,
+ csdWarningConfigModel,
+ uiEventLogger,
+ )
+ }
diff --git a/packages/SystemUI/utils/Android.bp b/packages/SystemUI/utils/Android.bp
index 1efb11b436ff..8b63c07b270f 100644
--- a/packages/SystemUI/utils/Android.bp
+++ b/packages/SystemUI/utils/Android.bp
@@ -26,6 +26,8 @@ java_library {
"src/**/*.kt",
],
static_libs: [
+ "//frameworks/libs/systemui:view_capture",
+ "com_android_systemui_flags_lib",
"kotlin-stdlib",
"kotlinx_coroutines",
],
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProvider.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProvider.kt
new file mode 100644
index 000000000000..4e6eacbc8808
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.utils.windowmanager
+
+import android.content.Context
+import android.view.WindowManager
+
+/**
+ * Provider for [WindowManager] in SystemUI.
+ *
+ * Use this class over [WindowManagerUtils] in cases where
+ * a [WindowManager] is needed for a context created inside the class. [WindowManagerUtils] should
+ * only be used in a class where the [WindowManager] is needed for a custom context inside the
+ * class, and the class is not part of the dagger graph. Example usage:
+ * ```kotlin
+ * class Sample {
+ * private final WindowManager mWindowManager;
+ *
+ * @Inject
+ * public Sample(WindowManagerProvider windowManagerProvider) {
+ * Context context = getCustomContext();
+ * mWindowManager = windowManagerProvider.getWindowManager(context);
+ * }
+ * // use mWindowManager
+ * }
+ *
+ * class SampleTest {
+ *
+ * @Mock
+ * WindowManager mWindowManager;
+ *
+ * FakeWindowManagerProvider fakeProvider = new FakeWindowManagerProvider(mWindowManager);
+ *
+ * // define the behaviour of mWindowManager to get required WindowManager instance in tests.
+ * }
+ * ```
+ */
+interface WindowManagerProvider {
+
+ /** Method to return the required [WindowManager]. */
+ fun getWindowManager(context: Context): WindowManager
+}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProviderImpl.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProviderImpl.kt
new file mode 100644
index 000000000000..5e965ed47403
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerProviderImpl.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.utils.windowmanager
+
+import android.content.Context
+import android.view.WindowManager
+
+/** Implementation of [WindowManagerProvider]. */
+class WindowManagerProviderImpl : WindowManagerProvider {
+
+ override fun getWindowManager(context: Context): WindowManager {
+ return WindowManagerUtils.getWindowManager(context)
+ }
+}
diff --git a/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerUtils.kt b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerUtils.kt
new file mode 100644
index 000000000000..643e93422294
--- /dev/null
+++ b/packages/SystemUI/utils/src/com/android/systemui/utils/windowmanager/WindowManagerUtils.kt
@@ -0,0 +1,49 @@
+/*
+ * 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.utils.windowmanager
+
+import android.content.Context
+import android.view.WindowManager
+import com.android.app.viewcapture.ViewCaptureAwareWindowManagerFactory
+import com.android.systemui.Flags.enableViewCaptureTracing
+
+/**
+ * Provides [WindowManager] in SystemUI. Use [WindowManagerProvider] unless [WindowManager] instance
+ * needs to be created in a class that is not part of the dagger dependency graph.
+ */
+object WindowManagerUtils {
+
+ /** Method to return the required [WindowManager]. */
+ @JvmStatic
+ fun getWindowManager(context: Context): WindowManager {
+ return if (!enableViewCaptureTracing()) {
+ context.getSystemService(WindowManager::class.java)
+ } else {
+ /**
+ * We use this token to supply windowContextToken to [WindowManager] for
+ * [WindowContext].
+ */
+ val windowContextToken = context.windowContextToken
+
+ ViewCaptureAwareWindowManagerFactory.getInstance(
+ context,
+ parent = null,
+ windowContextToken,
+ )
+ }
+ }
+}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
index f8bb526d0a86..760999f5e129 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
@@ -60,7 +60,7 @@ class ClassWidePolicyPropagatingFilter(
}
return p.withReason(policy.reason)
- .wrapReason("class-wide in $className")
+ .wrapReason("class-wide in $className", policy.statsLabelOverride)
}
// If the class's policy is remove, then remove it.
if (policy.policy == FilterPolicy.Remove) {
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
index 7358a0bfb3e6..e082bbb0a119 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -41,7 +41,7 @@ enum class StatsLabel(val statValue: Int, val label: String) {
data class FilterPolicyWithReason (
val policy: FilterPolicy,
val reason: String = "",
- private val statsLabelOverride: StatsLabel? = null
+ val statsLabelOverride: StatsLabel? = null
) {
/**
* Return a new [FilterPolicy] with an updated reason, while keeping the original reason
@@ -51,7 +51,7 @@ data class FilterPolicyWithReason (
return FilterPolicyWithReason(
policy,
"$reason [inner-reason: ${this.reason}]",
- statsLabelOverride = statsLabelOverride,
+ statsLabelOverride = statsLabelOverride ?: this.statsLabelOverride,
)
}
diff --git a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 97fc35302528..cdcea4c15820 100644
--- a/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/ravenwood/tools/hoststubgen/lib/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -528,7 +528,8 @@ class TextFileFilterPolicyParser {
)
}
val p = policy.withReason(
- "$FILTER_REASON (special-class AIDL)"
+ "$FILTER_REASON (special-class AIDL)",
+ StatsLabel.SupportedButBoring,
)
processor.onSpecialClassPolicy(classType, p)
aidlPolicy = p
@@ -541,7 +542,8 @@ class TextFileFilterPolicyParser {
)
}
val p = policy.withReason(
- "$FILTER_REASON (special-class feature flags)"
+ "$FILTER_REASON (special-class feature flags)",
+ StatsLabel.SupportedButBoring,
)
processor.onSpecialClassPolicy(classType, p)
featureFlagsPolicy = p
@@ -554,7 +556,8 @@ class TextFileFilterPolicyParser {
)
}
val p = policy.withReason(
- "$FILTER_REASON (special-class sysprops)"
+ "$FILTER_REASON (special-class sysprops)",
+ StatsLabel.SupportedButBoring,
)
processor.onSpecialClassPolicy(classType, p)
syspropsPolicy = p
@@ -567,7 +570,8 @@ class TextFileFilterPolicyParser {
)
}
val p = policy.withReason(
- "$FILTER_REASON (special-class R file)"
+ "$FILTER_REASON (special-class R file)",
+ StatsLabel.SupportedButBoring,
)
processor.onSpecialClassPolicy(classType, p)
rFilePolicy = p
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 47aa8f5736bf..aae8879e9199 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1942,14 +1942,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
}
public void notifyGesture(AccessibilityGestureEvent gestureEvent) {
- if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
- // We will use this event async, so copy it because it contains MotionEvents.
- mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
- gestureEvent.copyForAsync()).sendToTarget();
- } else {
- mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
- gestureEvent).sendToTarget();
- }
+ // We will use this event async, so copy it because it contains MotionEvents.
+ mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE,
+ gestureEvent.copyForAsync()).sendToTarget();
}
public void notifySystemActionsChangedLocked() {
@@ -2426,9 +2421,7 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ
case MSG_ON_GESTURE: {
if (message.obj instanceof AccessibilityGestureEvent gesture) {
notifyGestureInternal(gesture);
- if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
- gesture.recycle();
- }
+ gesture.recycle();
}
} break;
case MSG_CLEAR_ACCESSIBILITY_CACHE: {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 69ea12d86d47..39c1fa73b7ce 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -529,14 +529,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
- new InputManager.KeyGestureEventHandler() {
- @Override
- public boolean handleKeyGestureEvent(
- @NonNull KeyGestureEvent event,
- @Nullable IBinder focusedToken) {
- return AccessibilityManagerService.this.handleKeyGestureEvent(event);
- }
- };
+ (event, focusedToken) -> AccessibilityManagerService.this.handleKeyGestureEvent(event);
@VisibleForTesting
AccessibilityManagerService(
@@ -652,7 +645,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
new AccessibilityContentObserver(mMainHandler).register(
mContext.getContentResolver());
if (enableTalkbackAndMagnifierKeyGestures()) {
- mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ List<Integer> supportedGestures = List.of(
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAGNIFICATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK);
+ mInputManager.registerKeyGestureEventHandler(supportedGestures,
+ mKeyGestureEventHandler);
}
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
if (mHearingDeviceNotificationController != null) {
@@ -701,13 +698,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
}
@VisibleForTesting
- boolean handleKeyGestureEvent(KeyGestureEvent event) {
+ void handleKeyGestureEvent(KeyGestureEvent event) {
final boolean complete =
event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
&& !event.isCancelled();
final int gestureType = event.getKeyGestureType();
if (!complete) {
- return false;
+ return;
}
String targetName;
@@ -718,7 +715,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
case KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK:
targetName = mContext.getString(R.string.config_defaultSelectToSpeakService);
if (targetName.isEmpty()) {
- return false;
+ return;
}
final ComponentName targetServiceComponent = TextUtils.isEmpty(targetName)
@@ -730,7 +727,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
userState.getInstalledServiceInfoLocked(targetServiceComponent);
}
if (accessibilityServiceInfo == null) {
- return false;
+ return;
}
// Skip enabling if a warning dialog is required for the feature.
@@ -740,11 +737,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
Slog.w(LOG_TAG,
"Accessibility warning is required before this service can be "
+ "activated automatically via KEY_GESTURE shortcut.");
- return false;
+ return;
}
break;
default:
- return false;
+ Slog.w(LOG_TAG, "Received a key gesture " + event
+ + " that was not registered by this handler");
+ return;
}
List<String> shortcutTargets = getAccessibilityShortcutTargets(
@@ -763,14 +762,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// this will be a separate dialog that appears that requires the user to confirm
// which will resolve this race condition. For now, just require two presses the
// first time it is activated.
- return true;
+ return;
}
final int displayId = event.getDisplayId() != INVALID_DISPLAY
? event.getDisplayId() : getLastNonProxyTopFocusedDisplayId();
performAccessibilityShortcutInternal(displayId, KEY_GESTURE, targetName);
-
- return true;
}
@Override
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 0b9c45de6e40..60343e9e81e5 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -152,9 +152,20 @@ public class AutoclickController extends BaseEventStreamTransformation {
if (direction == AutoclickScrollPanel.DIRECTION_EXIT) {
return;
}
- // For direction buttons, perform scroll action immediately.
- if (hovered && direction != AutoclickScrollPanel.DIRECTION_NONE) {
- handleScroll(direction);
+
+ // Handle all non-exit buttons when hovered.
+ if (hovered) {
+ // Clear the indicator.
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.cancel();
+ if (mAutoclickIndicatorView != null) {
+ mAutoclickIndicatorView.clearIndicator();
+ }
+ }
+ // Perform scroll action.
+ if (direction != DIRECTION_NONE) {
+ handleScroll(direction);
+ }
}
}
};
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
index 3668eefe293d..62b6b85afa58 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java
@@ -336,13 +336,8 @@ public abstract class GestureMatcher {
// Recycle the old event first if necessary, to handle duplicate calls to post.
recycleEvent();
mTargetState = state;
- if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
- mEvent = event.copy();
- mRawEvent = rawEvent.copy();
- } else {
- mEvent = event;
- mRawEvent = rawEvent;
- }
+ mEvent = event.copy();
+ mRawEvent = rawEvent.copy();
mPolicyFlags = policyFlags;
mHandler.postDelayed(this, delay);
if (DEBUG) {
@@ -379,15 +374,13 @@ public abstract class GestureMatcher {
}
private void recycleEvent() {
- if (android.view.accessibility.Flags.copyEventsForGestureDetection()) {
- if (mEvent == null || mRawEvent == null) {
- return;
- }
- mEvent.recycle();
- mRawEvent.recycle();
- mEvent = null;
- mRawEvent = null;
+ if (mEvent == null || mRawEvent == null) {
+ return;
}
+ mEvent.recycle();
+ mRawEvent.recycle();
+ mEvent = null;
+ mRawEvent = null;
}
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index decac40d20f8..cf85dd957b3f 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -258,7 +258,6 @@ java_library_static {
"dreams_flags_lib",
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
- "locksettings_flags_lib",
"MmdProperties",
"mmd_flags_lib",
"profiling_flags_lib",
diff --git a/services/core/java/com/android/server/adb/AdbService.java b/services/core/java/com/android/server/adb/AdbService.java
index d12a0a2a1e00..c338a1ef15c9 100644
--- a/services/core/java/com/android/server/adb/AdbService.java
+++ b/services/core/java/com/android/server/adb/AdbService.java
@@ -32,6 +32,7 @@ import android.debug.IAdbTransport;
import android.debug.PairDevice;
import android.hardware.usb.UsbManager;
import android.net.Uri;
+import android.net.wifi.WifiManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
@@ -192,6 +193,7 @@ public class AdbService extends IAdbManager.Stub {
@Override
public void onChange(boolean selfChange, @NonNull Uri uri, @UserIdInt int userId) {
+ Slog.d("AdbSettingsObserver", "onChange " + uri.toString());
if (mAdbUsbUri.equals(uri)) {
boolean shouldEnable = (Settings.Global.getInt(mContentResolver,
Settings.Global.ADB_ENABLED, 0) > 0);
@@ -417,6 +419,28 @@ public class AdbService extends IAdbManager.Stub {
}
}
+ private WifiManager.MulticastLock mAdbMulticastLock = null;
+
+ private void acquireMulticastLock() {
+ if (mAdbMulticastLock == null) {
+ WifiManager wifiManager = (WifiManager)
+ mContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
+ mAdbMulticastLock = wifiManager.createMulticastLock("AdbMulticastLock");
+ }
+
+ if (!mAdbMulticastLock.isHeld()) {
+ mAdbMulticastLock.acquire();
+ Slog.d(TAG, "Acquired multicast lock");
+ }
+ }
+
+ private void releaseMulticastLock() {
+ if (mAdbMulticastLock != null && mAdbMulticastLock.isHeld()) {
+ mAdbMulticastLock.release();
+ Slog.d(TAG, "Released multicast lock");
+ }
+ }
+
private void setAdbEnabled(boolean enable, byte transportType) {
Slog.d(TAG, "setAdbEnabled(" + enable + "), mIsAdbUsbEnabled=" + mIsAdbUsbEnabled
+ ", mIsAdbWifiEnabled=" + mIsAdbWifiEnabled + ", transportType=" + transportType);
@@ -428,9 +452,11 @@ public class AdbService extends IAdbManager.Stub {
if (mIsAdbWifiEnabled) {
// Start adb over WiFi.
SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "1");
+ acquireMulticastLock();
} else {
// Stop adb over WiFi.
SystemProperties.set(WIFI_PERSISTENT_CONFIG_PROPERTY, "0");
+ releaseMulticastLock();
}
} else {
// No change
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 5395d2a914ec..c15915ba39a4 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -790,7 +790,7 @@ public final class ActiveServices {
"SHORT_FGS_TIMEOUT");
this.mServiceFGAnrTimer = new ServiceAnrTimer(service,
ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG,
- "SERVICE_FOREGROUND_TIMEOUT");
+ "SERVICE_FOREGROUND_TIMEOUT", new AnrTimer.Args().extend(true));
}
void systemServicesReady() {
@@ -7702,6 +7702,11 @@ public final class ActiveServices {
super(Objects.requireNonNull(am).mHandler, msg, label);
}
+ ServiceAnrTimer(ActivityManagerService am, int msg, String label,
+ @NonNull AnrTimer.Args args) {
+ super(Objects.requireNonNull(am).mHandler, msg, label, args);
+ }
+
@Override
public int getPid(@NonNull ServiceRecord service) {
return (service.app != null) ? service.app.getPid() : 0;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 6b3661a2a004..b1acfe830eed 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -186,6 +186,7 @@ import android.media.audiopolicy.AudioPolicyConfig;
import android.media.audiopolicy.AudioProductStrategy;
import android.media.audiopolicy.AudioVolumeGroup;
import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
import android.media.permission.ClearCallingIdentityContext;
import android.media.permission.SafeCloseable;
import android.media.projection.IMediaProjection;
@@ -1388,6 +1389,7 @@ public class AudioService extends IAudioService.Stub
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
+ mAudioVolumeChangeHandler = new AudioVolumeChangeHandler(mAudioSystem);
// Initialize volume
// Priority 1 - Android Property
// Priority 2 - Audio Policy Service
@@ -4188,7 +4190,13 @@ public class AudioService extends IAudioService.Stub
Log.d(TAG, "adjustStreamVolume postSetHearingAidVolumeIndex index="
+ newIndex + " stream=" + streamType);
}
- mDeviceBroker.postSetHearingAidVolumeIndex(newIndex, streamType);
+ int haIndex;
+ final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
+ synchronized (mVolumeStateLock) {
+ haIndex = (int) (vss.getMinIndex() + (newIndex - vss.getMinIndex())
+ / vss.getIndexStepFactor());
+ }
+ mDeviceBroker.postSetHearingAidVolumeIndex(haIndex, streamType);
}
}
@@ -4452,6 +4460,21 @@ public class AudioService extends IAudioService.Stub
}
}
+ //================================
+ // Audio Volume Change Dispatcher
+ //================================
+ private final AudioVolumeChangeHandler mAudioVolumeChangeHandler;
+
+ /** @see AudioManager#registerVolumeGroupCallback(executor, callback) */
+ public void registerAudioVolumeCallback(IAudioVolumeChangeDispatcher callback) {
+ mAudioVolumeChangeHandler.registerListener(callback);
+ }
+
+ /** @see AudioManager#unregisterVolumeGroupCallback(callback) */
+ public void unregisterAudioVolumeCallback(IAudioVolumeChangeDispatcher callback) {
+ mAudioVolumeChangeHandler.unregisterListener(callback);
+ }
+
@Override
@android.annotation.EnforcePermission(anyOf = {
MODIFY_AUDIO_SETTINGS_PRIVILEGED, MODIFY_AUDIO_ROUTING })
@@ -5126,7 +5149,13 @@ public class AudioService extends IAudioService.Stub
mDeviceBroker.postSetLeAudioVolumeIndex(index * 10,
getVssForStreamOrDefault(streamType).getMaxIndex(), streamType);
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
- mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
+ int haIndex = index * 10;
+ final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
+ synchronized (mVolumeStateLock) {
+ haIndex = (int) (vss.getMinIndex()
+ + (haIndex - vss.getMinIndex()) / vss.getIndexStepFactor());
+ }
+ mDeviceBroker.postSetHearingAidVolumeIndex(haIndex, streamType);
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index);
} else {
@@ -5257,7 +5286,13 @@ public class AudioService extends IAudioService.Stub
&& streamType == getBluetoothContextualVolumeStream()) {
Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+ " stream=" + streamType);
- mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
+ int haIndex;
+ final VolumeStreamState vss = getVssForStreamOrDefault(streamType);
+ synchronized (mVolumeStateLock) {
+ haIndex = (int) (vss.getMinIndex()
+ + (index - vss.getMinIndex()) / vss.getIndexStepFactor());
+ }
+ mDeviceBroker.postSetHearingAidVolumeIndex(haIndex, streamType);
}
synchronized (mHdmiClientLock) {
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index a6267c156fb3..ced5faeeff27 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -23,6 +23,7 @@ import android.media.AudioDeviceAttributes;
import android.media.AudioMixerAttributes;
import android.media.AudioSystem;
import android.media.IDevicesForAttributesCallback;
+import android.media.INativeAudioVolumeGroupCallback;
import android.media.ISoundDose;
import android.media.ISoundDoseCallback;
import android.media.audiopolicy.AudioMix;
@@ -758,6 +759,29 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback,
}
/**
+ * Same as {@link AudioSystem#registerAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback)}
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public int registerAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback callback) {
+ return AudioSystem.registerAudioVolumeGroupCallback(callback);
+ }
+
+ /**
+ * Same as
+ * {@link AudioSystem#unregisterAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback)}.
+ * @param callback to register
+ * @return {@link #SUCCESS} if successfully registered.
+ *
+ * @hide
+ */
+ public int unregisterAudioVolumeGroupCallback(INativeAudioVolumeGroupCallback callback) {
+ return AudioSystem.unregisterAudioVolumeGroupCallback(callback);
+ }
+
+ /**
* Part of AudioService dump
* @param pw
*/
diff --git a/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java b/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java
new file mode 100644
index 000000000000..2bb4301bb8fd
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioVolumeChangeHandler.java
@@ -0,0 +1,103 @@
+/*
+ * 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.audio;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.INativeAudioVolumeGroupCallback;
+import android.media.audio.common.AudioVolumeGroupChangeEvent;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The AudioVolumeChangeHandler handles AudioVolume callbacks invoked by native
+ * {@link INativeAudioVolumeGroupCallback} callback.
+ */
+/* private package */ class AudioVolumeChangeHandler {
+ private static final String TAG = "AudioVolumeChangeHandler";
+
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final RemoteCallbackList<IAudioVolumeChangeDispatcher> mListeners =
+ new RemoteCallbackList<>();
+ private final @NonNull AudioSystemAdapter mAudioSystem;
+ private @Nullable AudioVolumeGroupCallback mAudioVolumeGroupCallback;
+
+ AudioVolumeChangeHandler(@NonNull AudioSystemAdapter asa) {
+ mAudioSystem = asa;
+ }
+
+ @GuardedBy("mLock")
+ private void lazyInitLocked() {
+ mAudioVolumeGroupCallback = new AudioVolumeGroupCallback();
+ mAudioSystem.registerAudioVolumeGroupCallback(mAudioVolumeGroupCallback);
+ }
+
+ private void sendAudioVolumeGroupChangedToClients(int groupId, int index) {
+ RemoteCallbackList<IAudioVolumeChangeDispatcher> listeners;
+ int nbDispatchers;
+ synchronized (mLock) {
+ listeners = mListeners;
+ nbDispatchers = mListeners.beginBroadcast();
+ }
+ for (int i = 0; i < nbDispatchers; i++) {
+ try {
+ listeners.getBroadcastItem(i).onAudioVolumeGroupChanged(groupId, index);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to broadcast Volume Changed event");
+ }
+ }
+ synchronized (mLock) {
+ mListeners.finishBroadcast();
+ }
+ }
+
+ /**
+ * @param cb the {@link IAudioVolumeChangeDispatcher} to register
+ */
+ public void registerListener(@NonNull IAudioVolumeChangeDispatcher cb) {
+ Preconditions.checkNotNull(cb, "Volume group callback must not be null");
+ synchronized (mLock) {
+ if (mAudioVolumeGroupCallback == null) {
+ lazyInitLocked();
+ }
+ mListeners.register(cb);
+ }
+ }
+
+ /**
+ * @param cb the {@link IAudioVolumeChangeDispatcher} to unregister
+ */
+ public void unregisterListener(@NonNull IAudioVolumeChangeDispatcher cb) {
+ Preconditions.checkNotNull(cb, "Volume group callback must not be null");
+ synchronized (mLock) {
+ mListeners.unregister(cb);
+ }
+ }
+
+ private final class AudioVolumeGroupCallback extends INativeAudioVolumeGroupCallback.Stub {
+ public void onAudioVolumeGroupChanged(AudioVolumeGroupChangeEvent volumeEvent) {
+ Slog.v(TAG, "onAudioVolumeGroupChanged volumeEvent=" + volumeEvent);
+ sendAudioVolumeGroupChangedToClients(volumeEvent.groupId, volumeEvent.flags);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index a28069bbf050..95e58e1a7300 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -684,8 +684,9 @@ public final class DisplayManagerService extends SystemService {
final var backupManager = new BackupManager(mContext);
Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback =
update -> {
- if (mInputManagerInternal != null) {
- mInputManagerInternal.setDisplayTopology(update.second);
+ DisplayTopologyGraph graph = update.second;
+ if (mInputManagerInternal != null && graph != null) {
+ mInputManagerInternal.setDisplayTopology(graph);
}
deliverTopologyUpdate(update.first);
};
@@ -3647,7 +3648,7 @@ public final class DisplayManagerService extends SystemService {
private void deliverTopologyUpdate(DisplayTopology topology) {
if (DEBUG) {
- Slog.d(TAG, "Delivering topology update");
+ Slog.d(TAG, "Delivering topology update: " + topology);
}
if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
@@ -4209,13 +4210,18 @@ public final class DisplayManagerService extends SystemService {
public boolean mWifiDisplayScanRequested;
- // A single pending event.
+ // A single pending display event.
private record Event(int displayId, @DisplayEvent int event) { };
- // The list of pending events. This is null until there is a pending event to be saved.
- // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ // The list of pending display events. This is null until there is a pending event to be
+ // saved. This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
+ @GuardedBy("mCallback")
+ @Nullable
+ private ArrayList<Event> mPendingDisplayEvents;
+
@GuardedBy("mCallback")
- private ArrayList<Event> mPendingEvents;
+ @Nullable
+ private DisplayTopology mPendingTopology;
// Process states: a process is ready to receive events if it is neither cached nor
// frozen.
@@ -4285,7 +4291,10 @@ public final class DisplayManagerService extends SystemService {
*/
@GuardedBy("mCallback")
private boolean hasPendingAndIsReadyLocked() {
- return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive;
+ boolean pendingDisplayEvents = mPendingDisplayEvents != null
+ && !mPendingDisplayEvents.isEmpty();
+ boolean pendingTopology = mPendingTopology != null;
+ return isReadyLocked() && (pendingDisplayEvents || pendingTopology) && mAlive;
}
/**
@@ -4366,7 +4375,8 @@ public final class DisplayManagerService extends SystemService {
// occurs as the client is transitioning to ready but pending events have not
// been dispatched. The new event must be added to the pending list to
// preserve event ordering.
- if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) {
+ if (!isReadyLocked() || (mPendingDisplayEvents != null
+ && !mPendingDisplayEvents.isEmpty())) {
// The client is interested in the event but is not ready to receive it.
// Put the event on the pending list.
addDisplayEvent(displayId, event);
@@ -4453,13 +4463,13 @@ public final class DisplayManagerService extends SystemService {
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
@GuardedBy("mCallback")
private void addDisplayEvent(int displayId, int event) {
- if (mPendingEvents == null) {
- mPendingEvents = new ArrayList<>();
+ if (mPendingDisplayEvents == null) {
+ mPendingDisplayEvents = new ArrayList<>();
}
- if (!mPendingEvents.isEmpty()) {
+ if (!mPendingDisplayEvents.isEmpty()) {
// Ignore redundant events. Further optimization is possible by merging adjacent
// events.
- Event last = mPendingEvents.get(mPendingEvents.size() - 1);
+ Event last = mPendingDisplayEvents.get(mPendingDisplayEvents.size() - 1);
if (last.displayId == displayId && last.event == event) {
if (DEBUG) {
Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
@@ -4468,12 +4478,13 @@ public final class DisplayManagerService extends SystemService {
return;
}
}
- mPendingEvents.add(new Event(displayId, event));
+ mPendingDisplayEvents.add(new Event(displayId, event));
}
/**
* @return {@code false} if RemoteException happens; otherwise {@code true} for
- * success.
+ * success. This returns true even if the update was deferred because the remote client is
+ * cached or frozen.
*/
boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
if ((mInternalEventFlagsMask.get()
@@ -4490,6 +4501,18 @@ public final class DisplayManagerService extends SystemService {
// The client is not interested in this event, so do nothing.
return true;
}
+
+ if (deferDisplayEventsWhenFrozen()) {
+ synchronized (mCallback) {
+ // Save the new update if the client frozen or cached (not ready).
+ if (!isReadyLocked()) {
+ // The client is interested in the update but is not ready to receive it.
+ mPendingTopology = topology;
+ return true;
+ }
+ }
+ }
+
return transmitTopologyUpdate(topology);
}
@@ -4514,37 +4537,54 @@ public final class DisplayManagerService extends SystemService {
// would be unusual to do so. The method returns true on success.
// This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
public boolean dispatchPending() {
- Event[] pending;
+ Event[] pendingDisplayEvents = null;
+ DisplayTopology pendingTopology;
synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+ if (!mAlive) {
return true;
}
if (!isReadyLocked()) {
return false;
}
- pending = new Event[mPendingEvents.size()];
- pending = mPendingEvents.toArray(pending);
- mPendingEvents.clear();
+
+ if (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty()) {
+ pendingDisplayEvents = new Event[mPendingDisplayEvents.size()];
+ pendingDisplayEvents = mPendingDisplayEvents.toArray(pendingDisplayEvents);
+ mPendingDisplayEvents.clear();
+ }
+
+ pendingTopology = mPendingTopology;
+ mPendingTopology = null;
}
try {
- for (int i = 0; i < pending.length; i++) {
- Event displayEvent = pending[i];
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
- }
+ if (pendingDisplayEvents != null) {
+ for (int i = 0; i < pendingDisplayEvents.length; i++) {
+ Event displayEvent = pendingDisplayEvents[i];
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+
+ if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
+ continue;
+ }
- if (!shouldReceiveRefreshRateWithChangeUpdate(displayEvent.event)) {
- continue;
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
+ }
- transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
+ if (pendingTopology != null) {
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending topology: " + pendingTopology
+ + " to " + mUid + "/" + mPid);
+ }
+ mCallback.onTopologyChanged(pendingTopology);
}
+
return true;
} catch (RemoteException ex) {
- Slog.w(TAG, "Failed to notify process "
- + mPid + " that display topology changed, assuming it died.", ex);
+ Slog.w(TAG, "Failed to notify process " + mPid + ", assuming it died.", ex);
binderDied();
return false;
@@ -4556,11 +4596,12 @@ public final class DisplayManagerService extends SystemService {
if (deferDisplayEventsWhenFrozen()) {
final String fmt =
"mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
- + " cached=%s frozen=%s pending=%d";
+ + " cached=%s frozen=%s pendingDisplayEvents=%d pendingTopology=%b";
synchronized (mCallback) {
return formatSimple(fmt,
mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
- (mPendingEvents == null) ? 0 : mPendingEvents.size());
+ (mPendingDisplayEvents == null) ? 0 : mPendingDisplayEvents.size(),
+ mPendingTopology != null);
}
} else {
final String fmt =
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 60b7fca99e7b..228e6f1c4ddb 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -130,6 +130,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
private static final String OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE =
"fixed_content_mode";
+ /**
+ * When this flag is set, disables support for moving and resizing the overlay window.
+ * As the window is made non-touchable, this also makes it possible to directly interact with
+ * the content underneath.
+ */
+ private static final String OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION =
+ "disable_window_interaction";
+
// Gravity flags to decide where the overlay should be shown.
private static final String GRAVITY_TOP_LEFT = "gravity_top_left";
private static final String GRAVITY_BOTTOM_RIGHT = "gravity_bottom_right";
@@ -571,9 +579,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
@Override
public void run() {
OverlayMode mode = mModes.get(mActiveMode);
- OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),
- mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity,
- mFlags.mSecure, OverlayDisplayHandle.this);
+ OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), mName,
+ mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity, mFlags.mSecure,
+ mFlags.mDisableWindowInteraction, OverlayDisplayHandle.this);
window.show();
synchronized (getSyncRoot()) {
@@ -655,6 +663,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
/** See {@link #OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE}. */
final boolean mFixedContentMode;
+ /** See {@link #OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION}. */
+ final boolean mDisableWindowInteraction;
+
final int mGravity;
OverlayFlags(
@@ -662,11 +673,13 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
boolean ownContentOnly,
boolean shouldShowSystemDecorations,
boolean fixedContentMode,
+ boolean disableWindowInteraction,
int gravity) {
mSecure = secure;
mOwnContentOnly = ownContentOnly;
mShouldShowSystemDecorations = shouldShowSystemDecorations;
mFixedContentMode = fixedContentMode;
+ mDisableWindowInteraction = disableWindowInteraction;
mGravity = gravity;
}
@@ -677,6 +690,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
false /* ownContentOnly */,
false /* shouldShowSystemDecorations */,
false /* fixedContentMode */,
+ false /* disableWindowInteraction */,
Gravity.NO_GRAVITY);
}
@@ -684,6 +698,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
boolean ownContentOnly = false;
boolean shouldShowSystemDecorations = false;
boolean fixedContentMode = false;
+ boolean disableWindowInteraction = false;
int gravity = Gravity.NO_GRAVITY;
for (String flag: flagString.split(FLAG_SPLITTER)) {
if (OVERLAY_DISPLAY_FLAG_SECURE.equals(flag)) {
@@ -694,12 +709,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
shouldShowSystemDecorations = true;
} else if (OVERLAY_DISPLAY_FLAG_FIXED_CONTENT_MODE.equals(flag)) {
fixedContentMode = true;
+ } else if (OVERLAY_DISPLAY_FLAG_DISABLE_WINDOW_INTERACTION.equals(flag)) {
+ disableWindowInteraction = true;
} else {
gravity = parseOverlayGravity(flag);
}
}
return new OverlayFlags(secure, ownContentOnly, shouldShowSystemDecorations,
- fixedContentMode, gravity);
+ fixedContentMode, disableWindowInteraction, gravity);
}
@Override
@@ -709,6 +726,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter {
.append(", ownContentOnly=").append(mOwnContentOnly)
.append(", shouldShowSystemDecorations=").append(mShouldShowSystemDecorations)
.append(", fixedContentMode=").append(mFixedContentMode)
+ .append(", disableWindowInteraction=").append(mDisableWindowInteraction)
.append(", gravity").append(Gravity.toString(mGravity))
.append("}")
.toString();
diff --git a/services/core/java/com/android/server/display/OverlayDisplayWindow.java b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
index 3fd58e8641c3..523bbfa7d69a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayWindow.java
@@ -69,6 +69,7 @@ final class OverlayDisplayWindow implements DumpUtils.Dump {
private int mDensityDpi;
private final int mGravity;
private final boolean mSecure;
+ private final boolean mDisableWindowInteraction;
private final Listener mListener;
private String mTitle;
@@ -96,15 +97,15 @@ final class OverlayDisplayWindow implements DumpUtils.Dump {
private float mLiveTranslationY;
private float mLiveScale = 1.0f;
- public OverlayDisplayWindow(Context context, String name,
- int width, int height, int densityDpi, int gravity, boolean secure,
- Listener listener) {
+ OverlayDisplayWindow(Context context, String name, int width, int height, int densityDpi,
+ int gravity, boolean secure, boolean disableWindowInteraction, Listener listener) {
// Workaround device freeze (b/38372997)
ThreadedRenderer.disableVsync();
mContext = context;
mName = name;
mGravity = gravity;
mSecure = secure;
+ mDisableWindowInteraction = disableWindowInteraction;
mListener = listener;
mDisplayManager = (DisplayManager)context.getSystemService(
@@ -226,8 +227,10 @@ final class OverlayDisplayWindow implements DumpUtils.Dump {
if (mSecure) {
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE;
}
- if (DISABLE_MOVE_AND_RESIZE) {
+ if (DISABLE_MOVE_AND_RESIZE || mDisableWindowInteraction) {
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ mWindowParams.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
}
mWindowParams.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED;
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 c37733b05fba..2c90e1919123 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -156,6 +156,8 @@ public class DisplayModeDirector {
private SparseArray<Display.Mode> mDefaultModeByDisplay;
// a map from display id to display device config
private SparseArray<DisplayDeviceConfig> mDisplayDeviceConfigByDisplay = new SparseArray<>();
+ // set containing connected external display ids
+ private final Set<Integer> mExternalDisplaysConnected = new HashSet<>();
private SparseBooleanArray mHasArrSupport;
@@ -425,7 +427,7 @@ public class DisplayModeDirector {
// Some external displays physical refresh rate modes are slightly above 60hz.
// SurfaceFlinger will not enable these display modes unless it is configured to allow
// render rate at least at this frame rate.
- if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+ if (isExternalDisplayLocked(displayId)) {
primarySummary.maxRenderFrameRate = Math.max(baseMode.getRefreshRate(),
primarySummary.maxRenderFrameRate);
appRequestSummary.maxRenderFrameRate = Math.max(baseMode.getRefreshRate(),
@@ -653,6 +655,10 @@ public class DisplayModeDirector {
}
}
+ boolean isExternalDisplayLocked(int displayId) {
+ return mExternalDisplaysConnected.contains(displayId);
+ }
+
private static String switchingTypeToString(@DisplayManager.SwitchingType int type) {
switch (type) {
case DisplayManager.SWITCHING_TYPE_NONE:
@@ -694,6 +700,11 @@ public class DisplayModeDirector {
}
@VisibleForTesting
+ void addExternalDisplayId(int externalDisplayId) {
+ mExternalDisplaysConnected.add(externalDisplayId);
+ }
+
+ @VisibleForTesting
void injectBrightnessObserver(BrightnessObserver brightnessObserver) {
mBrightnessObserver = brightnessObserver;
}
@@ -1210,7 +1221,7 @@ public class DisplayModeDirector {
@GuardedBy("mLock")
private void updateRefreshRateSettingLocked(float minRefreshRate, float peakRefreshRate,
float defaultRefreshRate, int displayId) {
- if (mDisplayObserver.isExternalDisplayLocked(displayId)) {
+ if (isExternalDisplayLocked(displayId)) {
if (mLoggingEnabled) {
Slog.d(TAG, "skip updateRefreshRateSettingLocked for external display "
+ displayId);
@@ -1309,20 +1320,25 @@ public class DisplayModeDirector {
public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
Display.Mode requestedMode;
+ boolean isExternalDisplay;
synchronized (mLock) {
requestedMode = findModeLocked(displayId, modeId, requestedRefreshRate);
+ isExternalDisplay = isExternalDisplayLocked(displayId);
}
Vote frameRateVote = getFrameRateVote(
requestedMinRefreshRateRange, requestedMaxRefreshRateRange);
Vote baseModeRefreshRateVote = getBaseModeVote(requestedMode, requestedRefreshRate);
- Vote sizeVote = getSizeVote(requestedMode);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
frameRateVote);
mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
baseModeRefreshRateVote);
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+
+ if (!isExternalDisplay) {
+ Vote sizeVote = getSizeVote(requestedMode);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+ }
}
private Display.Mode findModeLocked(int displayId, int modeId, float requestedRefreshRate) {
@@ -1420,7 +1436,6 @@ public class DisplayModeDirector {
private int mExternalDisplayPeakHeight;
private int mExternalDisplayPeakRefreshRate;
private final boolean mRefreshRateSynchronizationEnabled;
- private final Set<Integer> mExternalDisplaysConnected = new HashSet<>();
DisplayObserver(Context context, Handler handler, VotesStorage votesStorage,
Injector injector) {
@@ -1541,10 +1556,6 @@ public class DisplayModeDirector {
}
}
- boolean isExternalDisplayLocked(int displayId) {
- return mExternalDisplaysConnected.contains(displayId);
- }
-
@Nullable
private DisplayInfo getDisplayInfo(int displayId) {
DisplayInfo info = new DisplayInfo();
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 41b0b4dc716a..a2d065400045 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -252,17 +252,22 @@ public class HdmiControlService extends SystemService {
static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_HDMI_EARC =
new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
AudioDeviceInfo.TYPE_HDMI_EARC, "");
+ static final AudioDeviceAttributes AUDIO_OUTPUT_DEVICE_LINE_DIGITAL =
+ new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ AudioDeviceInfo.TYPE_LINE_DIGITAL, "");
// Audio output devices used for absolute volume behavior
private static final List<AudioDeviceAttributes> AVB_AUDIO_OUTPUT_DEVICES =
List.of(AUDIO_OUTPUT_DEVICE_HDMI,
AUDIO_OUTPUT_DEVICE_HDMI_ARC,
- AUDIO_OUTPUT_DEVICE_HDMI_EARC);
+ AUDIO_OUTPUT_DEVICE_HDMI_EARC,
+ AUDIO_OUTPUT_DEVICE_LINE_DIGITAL);
// Audio output devices used for absolute volume behavior on TV panels
private static final List<AudioDeviceAttributes> TV_AVB_AUDIO_OUTPUT_DEVICES =
List.of(AUDIO_OUTPUT_DEVICE_HDMI_ARC,
- AUDIO_OUTPUT_DEVICE_HDMI_EARC);
+ AUDIO_OUTPUT_DEVICE_HDMI_EARC,
+ AUDIO_OUTPUT_DEVICE_LINE_DIGITAL);
// Audio output devices used for absolute volume behavior on Playback devices
private static final List<AudioDeviceAttributes> PLAYBACK_AVB_AUDIO_OUTPUT_DEVICES =
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
index 8c028bc92841..eb102294ac32 100644
--- a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -111,7 +111,7 @@ final class AppLaunchShortcutManager {
mContext = context;
}
- public void systemRunning() {
+ public void init() {
loadShortcuts();
}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 67e1ccc6a850..e6d71900f106 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -94,9 +94,9 @@ final class InputGestureManager {
mContext = context;
}
- public void systemRunning() {
+ public void init(List<InputGestureData> bookmarks) {
initSystemShortcuts();
- blockListBookmarkedTriggers();
+ blockListBookmarkedTriggers(bookmarks);
}
private void initSystemShortcuts() {
@@ -263,10 +263,9 @@ final class InputGestureManager {
}
}
- private void blockListBookmarkedTriggers() {
+ private void blockListBookmarkedTriggers(List<InputGestureData> bookmarks) {
synchronized (mGestureLock) {
- InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
+ for (InputGestureData bookmark : bookmarks) {
mBlockListedTriggers.add(bookmark.getTrigger());
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 6e6d00d62819..6af55300d0b3 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -25,6 +25,7 @@ import static android.view.KeyEvent.KEYCODE_UNKNOWN;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.hardware.input.Flags.enableCustomizableInputGestures;
+import static com.android.hardware.input.Flags.fixSearchModifierFallbacks;
import static com.android.hardware.input.Flags.keyEventActivityDetection;
import static com.android.hardware.input.Flags.touchpadVisualizer;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
@@ -750,7 +751,8 @@ public class InputManagerService extends IInputManager.Stub
* @return True if the lookup was successful, false otherwise.
*/
@Override // Binder call
- public boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists) {
+ public boolean hasKeys(int deviceId, int sourceMask, @NonNull int[] keyCodes,
+ @NonNull boolean[] keyExists) {
Objects.requireNonNull(keyCodes, "keyCodes must not be null");
Objects.requireNonNull(keyExists, "keyExists must not be null");
if (keyExists.length < keyCodes.length) {
@@ -791,7 +793,7 @@ public class InputManagerService extends IInputManager.Stub
* @deprecated Use {@link #transferTouchGesture(IBinder, IBinder)}
*/
@Deprecated
- public boolean transferTouch(IBinder destChannelToken, int displayId) {
+ public boolean transferTouch(@NonNull IBinder destChannelToken, int displayId) {
// TODO(b/162194035): Replace this with a SPY window
Objects.requireNonNull(destChannelToken, "destChannelToken must not be null");
return mNative.transferTouch(destChannelToken, displayId);
@@ -803,7 +805,7 @@ public class InputManagerService extends IInputManager.Stub
* @param displayId Target display id.
* @return The input channel.
*/
- public InputChannel monitorInput(String inputChannelName, int displayId) {
+ public InputChannel monitorInput(@NonNull String inputChannelName, int displayId) {
Objects.requireNonNull(inputChannelName, "inputChannelName not be null");
if (displayId < Display.DEFAULT_DISPLAY) {
@@ -835,7 +837,7 @@ public class InputManagerService extends IInputManager.Stub
return outInputChannel;
}
- private void removeSpyWindowGestureMonitor(IBinder inputChannelToken) {
+ private void removeSpyWindowGestureMonitor(@NonNull IBinder inputChannelToken) {
final GestureMonitorSpyWindow monitor;
synchronized (mInputMonitors) {
monitor = mInputMonitors.remove(inputChannelToken);
@@ -854,8 +856,8 @@ public class InputManagerService extends IInputManager.Stub
* @return The input channel.
*/
@Override // Binder call
- public InputMonitor monitorGestureInput(IBinder monitorToken, @NonNull String requestedName,
- int displayId) {
+ public InputMonitor monitorGestureInput(@NonNull IBinder monitorToken,
+ @NonNull String requestedName, int displayId) {
if (!checkCallingPermission(android.Manifest.permission.MONITOR_INPUT,
"monitorGestureInput()")) {
throw new SecurityException("Requires MONITOR_INPUT permission");
@@ -902,7 +904,7 @@ public class InputManagerService extends IInputManager.Stub
* Removes an input channel.
* @param connectionToken The input channel to unregister.
*/
- public void removeInputChannel(IBinder connectionToken) {
+ public void removeInputChannel(@NonNull IBinder connectionToken) {
Objects.requireNonNull(connectionToken, "connectionToken must not be null");
mNative.removeInputChannel(connectionToken);
}
@@ -977,12 +979,12 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public boolean injectInputEvent(InputEvent event, int mode) {
+ public boolean injectInputEvent(@NonNull InputEvent event, int mode) {
return injectInputEventToTarget(event, mode, Process.INVALID_UID);
}
@Override // Binder call
- public boolean injectInputEventToTarget(InputEvent event, int mode, int targetUid) {
+ public boolean injectInputEventToTarget(@NonNull InputEvent event, int mode, int targetUid) {
if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
"injectInputEvent()", true /*checkInstrumentationSource*/)) {
throw new SecurityException(
@@ -1032,7 +1034,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public VerifiedInputEvent verifyInputEvent(InputEvent event) {
+ public VerifiedInputEvent verifyInputEvent(@NonNull InputEvent event) {
Objects.requireNonNull(event, "event must not be null");
return mNative.verifyInputEvent(event);
}
@@ -1106,7 +1108,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public void registerInputDevicesChangedListener(IInputDevicesChangedListener listener) {
+ public void registerInputDevicesChangedListener(
+ @NonNull IInputDevicesChangedListener listener) {
Objects.requireNonNull(listener, "listener must not be null");
synchronized (mInputDevicesLock) {
@@ -1176,7 +1179,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call & native callback
- public TouchCalibration getTouchCalibrationForInputDevice(String inputDeviceDescriptor,
+ public TouchCalibration getTouchCalibrationForInputDevice(@NonNull String inputDeviceDescriptor,
int surfaceRotation) {
Objects.requireNonNull(inputDeviceDescriptor, "inputDeviceDescriptor must not be null");
@@ -1186,8 +1189,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public void setTouchCalibrationForInputDevice(String inputDeviceDescriptor, int surfaceRotation,
- TouchCalibration calibration) {
+ public void setTouchCalibrationForInputDevice(@NonNull String inputDeviceDescriptor,
+ int surfaceRotation, @NonNull TouchCalibration calibration) {
if (!checkCallingPermission(android.Manifest.permission.SET_INPUT_CALIBRATION,
"setTouchCalibrationForInputDevice()")) {
throw new SecurityException("Requires SET_INPUT_CALIBRATION permission");
@@ -1225,7 +1228,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public void registerTabletModeChangedListener(ITabletModeChangedListener listener) {
+ public void registerTabletModeChangedListener(@NonNull ITabletModeChangedListener listener) {
if (!checkCallingPermission(android.Manifest.permission.TABLET_MODE,
"registerTabletModeChangedListener()")) {
throw new SecurityException("Requires TABLET_MODE_LISTENER permission");
@@ -1341,7 +1344,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void requestPointerCapture(IBinder inputChannelToken, boolean enabled) {
+ public void requestPointerCapture(@NonNull IBinder inputChannelToken, boolean enabled) {
Objects.requireNonNull(inputChannelToken, "inputChannelToken must not be null");
mNative.requestPointerCapture(inputChannelToken, enabled);
@@ -1664,7 +1667,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) {
+ public boolean registerVibratorStateListener(int deviceId,
+ @NonNull IVibratorStateListener listener) {
Objects.requireNonNull(listener, "listener must not be null");
RemoteCallbackList<IVibratorStateListener> listeners;
@@ -1717,8 +1721,8 @@ public class InputManagerService extends IInputManager.Stub
// Binder call
@Override
- public boolean setPointerIcon(PointerIcon icon, int displayId, int deviceId, int pointerId,
- IBinder inputToken) {
+ public boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId,
+ int pointerId, IBinder inputToken) {
Objects.requireNonNull(icon);
return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
}
@@ -1896,7 +1900,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public boolean registerSensorListener(IInputSensorEventListener listener) {
+ public boolean registerSensorListener(@NonNull IInputSensorEventListener listener) {
if (DEBUG) {
Slog.d(TAG, "registerSensorListener: listener=" + listener + " callingPid="
+ Binder.getCallingPid());
@@ -1927,7 +1931,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override // Binder call
- public void unregisterSensorListener(IInputSensorEventListener listener) {
+ public void unregisterSensorListener(@NonNull IInputSensorEventListener listener) {
if (DEBUG) {
Slog.d(TAG, "unregisterSensorListener: listener=" + listener + " callingPid="
+ Binder.getCallingPid());
@@ -2016,7 +2020,8 @@ public class InputManagerService extends IInputManager.Stub
/**
* Set specified light state with for a specific input device.
*/
- private void setLightStateInternal(int deviceId, Light light, LightState lightState) {
+ private void setLightStateInternal(int deviceId, @NonNull Light light,
+ @NonNull LightState lightState) {
Objects.requireNonNull(light, "light does not exist");
if (DEBUG) {
Slog.d(TAG, "setLightStateInternal device " + deviceId + " light " + light
@@ -2079,7 +2084,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void openLightSession(int deviceId, String opPkg, IBinder token) {
+ public void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) {
Objects.requireNonNull(token);
synchronized (mLightLock) {
Preconditions.checkState(mLightSessions.get(token) == null, "already registered");
@@ -2098,7 +2103,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void closeLightSession(int deviceId, IBinder token) {
+ public void closeLightSession(int deviceId, @NonNull IBinder token) {
Objects.requireNonNull(token);
synchronized (mLightLock) {
LightSession lightSession = mLightSessions.get(token);
@@ -2128,13 +2133,15 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ public void registerBatteryListener(int deviceId,
+ @NonNull IInputDeviceBatteryListener listener) {
Objects.requireNonNull(listener);
mBatteryController.registerBatteryListener(deviceId, listener, Binder.getCallingPid());
}
@Override
- public void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener) {
+ public void unregisterBatteryListener(int deviceId,
+ @NonNull IInputDeviceBatteryListener listener) {
Objects.requireNonNull(listener);
mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
}
@@ -2155,7 +2162,7 @@ public class InputManagerService extends IInputManager.Stub
@EnforcePermission(Manifest.permission.MONITOR_INPUT)
@Override
- public void pilferPointers(IBinder inputChannelToken) {
+ public void pilferPointers(@NonNull IBinder inputChannelToken) {
super.pilferPointers_enforcePermission();
Objects.requireNonNull(inputChannelToken);
@@ -2164,7 +2171,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
@EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
- public void registerKeyboardBacklightListener(IKeyboardBacklightListener listener) {
+ public void registerKeyboardBacklightListener(@NonNull IKeyboardBacklightListener listener) {
super.registerKeyboardBacklightListener_enforcePermission();
Objects.requireNonNull(listener);
mKeyboardBacklightController.registerKeyboardBacklightListener(listener,
@@ -2173,7 +2180,7 @@ public class InputManagerService extends IInputManager.Stub
@Override
@EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_BACKLIGHT)
- public void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener) {
+ public void unregisterKeyboardBacklightListener(@NonNull IKeyboardBacklightListener listener) {
super.unregisterKeyboardBacklightListener_enforcePermission();
Objects.requireNonNull(listener);
mKeyboardBacklightController.unregisterKeyboardBacklightListener(listener,
@@ -2653,6 +2660,8 @@ public class InputManagerService extends IInputManager.Stub
@SuppressWarnings("unused")
@VisibleForTesting
long interceptKeyBeforeDispatching(IBinder focus, KeyEvent event, int policyFlags) {
+ final long keyNotConsumedGoFallback = -2;
+ final long keyConsumed = -1;
final long keyNotConsumed = 0;
long value = keyNotConsumed;
// TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
@@ -2667,6 +2676,16 @@ public class InputManagerService extends IInputManager.Stub
value = mWindowManagerCallbacks.interceptKeyBeforeDispatching(focus, event,
policyFlags);
}
+ if (fixSearchModifierFallbacks() && value == keyNotConsumed && event.isMetaPressed()) {
+ // If the key has not been consumed and includes the meta key, do not send the event
+ // to the app and attempt to generate a fallback.
+ final KeyCharacterMap kcm = event.getKeyCharacterMap();
+ final KeyCharacterMap.FallbackAction fallbackAction =
+ kcm.getFallbackAction(event.getKeyCode(), event.getMetaState());
+ if (fallbackAction != null) {
+ return keyNotConsumedGoFallback;
+ }
+ }
return value;
}
@@ -2751,18 +2770,23 @@ public class InputManagerService extends IInputManager.Stub
@SuppressLint("MissingPermission")
private void initKeyGestures() {
InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- im.registerKeyGestureEventHandler(new InputManager.KeyGestureEventHandler() {
- @Override
- public boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event,
- @Nullable IBinder focussedToken) {
- return InputManagerService.this.handleKeyGestureEvent(event);
- }
- });
+ List<Integer> supportedGestures = List.of(
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+ );
+ im.registerKeyGestureEventHandler(supportedGestures,
+ (event, focusedToken) -> InputManagerService.this.handleKeyGestureEvent(event));
}
@SuppressLint("MissingPermission")
@VisibleForTesting
- boolean handleKeyGestureEvent(@NonNull KeyGestureEvent event) {
+ void handleKeyGestureEvent(@NonNull KeyGestureEvent event) {
int deviceId = event.getDeviceId();
boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
&& !event.isCancelled();
@@ -2771,20 +2795,20 @@ public class InputManagerService extends IInputManager.Stub
if (complete) {
mKeyboardBacklightController.incrementKeyboardBacklight(deviceId);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
if (complete) {
mKeyboardBacklightController.decrementKeyboardBacklight(deviceId);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
// TODO(b/367748270): Add functionality to turn keyboard backlight on/off.
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
if (complete) {
mNative.toggleCapsLock(deviceId);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS:
if (complete) {
final boolean bounceKeysEnabled =
@@ -2792,7 +2816,6 @@ public class InputManagerService extends IInputManager.Stub
InputSettings.setAccessibilityBounceKeysThreshold(mContext,
bounceKeysEnabled ? 0
: InputSettings.DEFAULT_BOUNCE_KEYS_THRESHOLD_MILLIS);
- return true;
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS:
@@ -2800,7 +2823,6 @@ public class InputManagerService extends IInputManager.Stub
final boolean mouseKeysEnabled = InputSettings.isAccessibilityMouseKeysEnabled(
mContext);
InputSettings.setAccessibilityMouseKeysEnabled(mContext, !mouseKeysEnabled);
- return true;
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS:
@@ -2808,7 +2830,6 @@ public class InputManagerService extends IInputManager.Stub
final boolean stickyKeysEnabled =
InputSettings.isAccessibilityStickyKeysEnabled(mContext);
InputSettings.setAccessibilityStickyKeysEnabled(mContext, !stickyKeysEnabled);
- return true;
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS:
@@ -2817,14 +2838,13 @@ public class InputManagerService extends IInputManager.Stub
InputSettings.isAccessibilitySlowKeysEnabled(mContext);
InputSettings.setAccessibilitySlowKeysThreshold(mContext,
slowKeysEnabled ? 0 : InputSettings.DEFAULT_SLOW_KEYS_THRESHOLD_MILLIS);
- return true;
}
break;
default:
- return false;
-
+ Log.w(TAG, "Received a key gesture " + event
+ + " that was not registered by this handler");
+ break;
}
- return false;
}
// Native callback.
@@ -3147,11 +3167,14 @@ public class InputManagerService extends IInputManager.Stub
@Override
@PermissionManuallyEnforced
- public void registerKeyGestureHandler(@NonNull IKeyGestureHandler handler) {
+ public void registerKeyGestureHandler(int[] keyGesturesToHandle,
+ @NonNull IKeyGestureHandler handler) {
enforceManageKeyGesturePermission();
Objects.requireNonNull(handler);
- mKeyGestureController.registerKeyGestureHandler(handler, Binder.getCallingPid());
+ Objects.requireNonNull(keyGesturesToHandle);
+ mKeyGestureController.registerKeyGestureHandler(keyGesturesToHandle, handler,
+ Binder.getCallingPid());
}
@Override
@@ -3306,9 +3329,10 @@ public class InputManagerService extends IInputManager.Stub
* @param token the window token that's about to receive this event
* @param event the key event that's being dispatched
* @param policyFlags the policy flags
- * @return negative value if the key should be skipped (not sent to the app). 0 if the key
- * should proceed getting dispatched to the app. positive value to indicate the additional
- * time delay, in nanoseconds, to wait before sending this key to the app.
+ * @return -1 if the key should be skipped (not sent to the app). -2 if the key should not
+ * be sent to the app, but it should still generate a fallback.
+ * 0 if the key should proceed getting dispatched to the app. positive value to indicate the
+ * additional time delay, in nanoseconds, to wait before sending this key to the app.
*/
long interceptKeyBeforeDispatching(IBinder token, KeyEvent event, int policyFlags);
@@ -3425,7 +3449,7 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void sendInputEvent(InputEvent event, int policyFlags) {
+ public void sendInputEvent(@NonNull InputEvent event, int policyFlags) {
if (!checkCallingPermission(android.Manifest.permission.INJECT_EVENTS,
"sendInputEvent()")) {
throw new SecurityException(
@@ -3447,9 +3471,9 @@ public class InputManagerService extends IInputManager.Stub
* Interface for the system to handle request from InputMonitors.
*/
private final class InputMonitorHost extends IInputMonitorHost.Stub {
- private final IBinder mInputChannelToken;
+ private final @NonNull IBinder mInputChannelToken;
- InputMonitorHost(IBinder inputChannelToken) {
+ InputMonitorHost(@NonNull IBinder inputChannelToken) {
mInputChannelToken = inputChannelToken;
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 395c77322c04..5de432e5849b 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -58,6 +58,7 @@ import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -79,11 +80,11 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
-import java.util.TreeMap;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
@@ -166,11 +167,14 @@ final class KeyGestureController {
private final SparseArray<KeyGestureEventListenerRecord>
mKeyGestureEventListenerRecords = new SparseArray<>();
- // List of currently registered key gesture event handler keyed by process pid. The map sorts
- // in the order of preference of the handlers, and we prioritize handlers in system server
- // over external handlers..
+ // Map of currently registered key gesture event handlers keyed by pid.
@GuardedBy("mKeyGestureHandlerRecords")
- private final TreeMap<Integer, KeyGestureHandlerRecord> mKeyGestureHandlerRecords;
+ private final SparseArray<KeyGestureHandlerRecord> mKeyGestureHandlerRecords =
+ new SparseArray<>();
+
+ // Currently supported key gestures mapped to pid that registered the corresponding handler.
+ @GuardedBy("mKeyGestureHandlerRecords")
+ private final SparseIntArray mSupportedKeyGestureToPidMap = new SparseIntArray();
private final ArrayDeque<KeyGestureEvent> mLastHandledEvents = new ArrayDeque<>();
@@ -193,18 +197,6 @@ final class KeyGestureController {
mHandler = new Handler(looper, this::handleMessage);
mIoHandler = new Handler(ioLooper, this::handleIoMessage);
mSystemPid = Process.myPid();
- mKeyGestureHandlerRecords = new TreeMap<>((p1, p2) -> {
- if (Objects.equals(p1, p2)) {
- return 0;
- }
- if (p1 == mSystemPid) {
- return -1;
- } else if (p2 == mSystemPid) {
- return 1;
- } else {
- return Integer.compare(p1, p2);
- }
- });
mKeyCombinationManager = new KeyCombinationManager(mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
@@ -450,8 +442,8 @@ final class KeyGestureController {
public void systemRunning() {
mSettingsObserver.observe();
- mAppLaunchShortcutManager.systemRunning();
- mInputGestureManager.systemRunning();
+ mAppLaunchShortcutManager.init();
+ mInputGestureManager.init(mAppLaunchShortcutManager.getBookmarks());
initKeyGestures();
int userId;
@@ -465,22 +457,24 @@ final class KeyGestureController {
@SuppressLint("MissingPermission")
private void initKeyGestures() {
InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
- im.registerKeyGestureEventHandler((event, focusedToken) -> {
- switch (event.getKeyGestureType()) {
- case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD:
- if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) {
- mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
- mHandler.sendMessageDelayed(
- mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
- getAccessibilityShortcutTimeout());
+ im.registerKeyGestureEventHandler(
+ List.of(KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD),
+ (event, focusedToken) -> {
+ if (event.getKeyGestureType()
+ == KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD) {
+ if (event.getAction() == KeyGestureEvent.ACTION_GESTURE_START) {
+ mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ mHandler.sendMessageDelayed(
+ mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT),
+ getAccessibilityShortcutTimeout());
+ } else {
+ mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ }
} else {
- mHandler.removeMessages(MSG_ACCESSIBILITY_SHORTCUT);
+ Log.w(TAG, "Received a key gesture " + event
+ + " that was not registered by this handler");
}
- return true;
- default:
- return false;
- }
- });
+ });
}
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -590,10 +584,11 @@ final class KeyGestureController {
return true;
}
if (result.appLaunchData() != null) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, result.appLaunchData());
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken, /* flags = */
+ 0, result.appLaunchData());
+ return true;
}
// Handle system shortcuts
@@ -601,11 +596,11 @@ final class KeyGestureController {
InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent(
event);
if (systemShortcut != null) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
systemShortcut.getAction().keyGestureType(),
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0,
- systemShortcut.getAction().appLaunchData());
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, systemShortcut.getAction().appLaunchData());
+ return true;
}
}
@@ -687,11 +682,11 @@ final class KeyGestureController {
return true;
case KeyEvent.KEYCODE_SEARCH:
if (firstDown && mSearchKeyBehavior == SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
-
+ return true;
}
break;
case KeyEvent.KEYCODE_SETTINGS:
@@ -782,11 +777,12 @@ final class KeyGestureController {
if (KeyEvent.metaStateHasModifiers(
shiftlessModifiers, KeyEvent.META_ALT_ON)) {
mPendingHideRecentSwitcher = true;
- return handleKeyGesture(deviceId, new int[]{keyCode},
+ handleKeyGesture(deviceId, new int[]{keyCode},
KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
KeyGestureEvent.ACTION_GESTURE_START, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
+ return true;
}
}
}
@@ -803,21 +799,23 @@ final class KeyGestureController {
} else {
if (mPendingHideRecentSwitcher) {
mPendingHideRecentSwitcher = false;
- return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB},
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_TAB},
KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
+ return true;
}
// Toggle Caps Lock on META-ALT.
if (mPendingCapsLockToggle) {
mPendingCapsLockToggle = false;
- return handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT,
+ handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_META_LEFT,
KeyEvent.KEYCODE_ALT_LEFT}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
+ return true;
}
}
break;
@@ -885,11 +883,11 @@ final class KeyGestureController {
if (customGesture == null) {
return false;
}
- return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
customGesture.getAction().keyGestureType(),
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0,
- customGesture.getAction().appLaunchData());
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
+ /* flags = */0, customGesture.getAction().appLaunchData());
+ return true;
}
return false;
}
@@ -908,7 +906,7 @@ final class KeyGestureController {
// Handle keyboard layout switching. (CTRL + SPACE)
if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
KeyEvent.META_CTRL_ON)) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
+ handleKeyGesture(deviceId, new int[]{keyCode},
KeyEvent.META_CTRL_ON | (event.isShiftPressed()
? KeyEvent.META_SHIFT_ON : 0),
KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
@@ -921,7 +919,7 @@ final class KeyGestureController {
if (down && KeyEvent.metaStateHasModifiers(metaState,
KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON)) {
// Intercept the Accessibility keychord (CTRL + ALT + Z) for keyboard users.
- return handleKeyGesture(deviceId, new int[]{keyCode},
+ handleKeyGesture(deviceId, new int[]{keyCode},
KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
@@ -930,7 +928,7 @@ final class KeyGestureController {
break;
case KeyEvent.KEYCODE_SYSRQ:
if (down && repeatCount == 0) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
@@ -938,7 +936,7 @@ final class KeyGestureController {
break;
case KeyEvent.KEYCODE_ESCAPE:
if (down && KeyEvent.metaStateHasNoModifiers(metaState) && repeatCount == 0) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
+ handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
focusedToken, /* flags = */0, /* appLaunchData = */null);
@@ -964,29 +962,31 @@ final class KeyGestureController {
}
@VisibleForTesting
- boolean handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
+ void handleKeyGesture(int deviceId, int[] keycodes, int modifierState,
@KeyGestureEvent.KeyGestureType int gestureType, int action, int displayId,
@Nullable IBinder focusedToken, int flags, @Nullable AppLaunchData appLaunchData) {
- return handleKeyGesture(createKeyGestureEvent(deviceId, keycodes,
- modifierState, gestureType, action, displayId, flags, appLaunchData), focusedToken);
+ handleKeyGesture(
+ createKeyGestureEvent(deviceId, keycodes, modifierState, gestureType, action,
+ displayId, flags, appLaunchData), focusedToken);
}
- private boolean handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
+ private void handleKeyGesture(AidlKeyGestureEvent event, @Nullable IBinder focusedToken) {
if (mVisibleBackgroundUsersEnabled && event.displayId != DEFAULT_DISPLAY
&& shouldIgnoreGestureEventForVisibleBackgroundUser(event.gestureType,
event.displayId)) {
- return false;
+ return;
}
synchronized (mKeyGestureHandlerRecords) {
- for (KeyGestureHandlerRecord handler : mKeyGestureHandlerRecords.values()) {
- if (handler.handleKeyGesture(event, focusedToken)) {
- Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT, event);
- mHandler.sendMessage(msg);
- return true;
- }
+ int index = mSupportedKeyGestureToPidMap.indexOfKey(event.gestureType);
+ if (index < 0) {
+ Log.i(TAG, "Key gesture: " + event.gestureType + " is not supported");
+ return;
}
+ int pid = mSupportedKeyGestureToPidMap.valueAt(index);
+ mKeyGestureHandlerRecords.get(pid).handleKeyGesture(event, focusedToken);
+ Message msg = Message.obtain(mHandler, MSG_NOTIFY_KEY_GESTURE_EVENT, event);
+ mHandler.sendMessage(msg);
}
- return false;
}
private boolean shouldIgnoreGestureEventForVisibleBackgroundUser(
@@ -1285,12 +1285,23 @@ final class KeyGestureController {
/** Register the key gesture event handler for a process. */
@BinderThread
- public void registerKeyGestureHandler(IKeyGestureHandler handler, int pid) {
+ public void registerKeyGestureHandler(int[] keyGesturesToHandle, IKeyGestureHandler handler,
+ int pid) {
synchronized (mKeyGestureHandlerRecords) {
if (mKeyGestureHandlerRecords.get(pid) != null) {
throw new IllegalStateException("The calling process has already registered "
+ "a KeyGestureHandler.");
}
+ if (keyGesturesToHandle.length == 0) {
+ throw new IllegalArgumentException("No key gestures provided for pid = " + pid);
+ }
+ for (int gestureType : keyGesturesToHandle) {
+ if (mSupportedKeyGestureToPidMap.indexOfKey(gestureType) >= 0) {
+ throw new IllegalArgumentException(
+ "Key gesture " + gestureType + " is already registered by pid = "
+ + mSupportedKeyGestureToPidMap.get(gestureType));
+ }
+ }
KeyGestureHandlerRecord record = new KeyGestureHandlerRecord(pid, handler);
try {
handler.asBinder().linkToDeath(record, 0);
@@ -1298,6 +1309,9 @@ final class KeyGestureController {
throw new RuntimeException(ex);
}
mKeyGestureHandlerRecords.put(pid, record);
+ for (int gestureType : keyGesturesToHandle) {
+ mSupportedKeyGestureToPidMap.put(gestureType, pid);
+ }
}
}
@@ -1315,7 +1329,7 @@ final class KeyGestureController {
+ "KeyGestureHandler.");
}
record.mKeyGestureHandler.asBinder().unlinkToDeath(record, 0);
- mKeyGestureHandlerRecords.remove(pid);
+ onKeyGestureHandlerRemoved(pid);
}
}
@@ -1328,9 +1342,14 @@ final class KeyGestureController {
return mAppLaunchShortcutManager.getBookmarks();
}
- private void onKeyGestureHandlerDied(int pid) {
+ private void onKeyGestureHandlerRemoved(int pid) {
synchronized (mKeyGestureHandlerRecords) {
mKeyGestureHandlerRecords.remove(pid);
+ for (int i = mSupportedKeyGestureToPidMap.size() - 1; i >= 0; i--) {
+ if (mSupportedKeyGestureToPidMap.valueAt(i) == pid) {
+ mSupportedKeyGestureToPidMap.removeAt(i);
+ }
+ }
}
}
@@ -1369,18 +1388,17 @@ final class KeyGestureController {
if (DEBUG) {
Slog.d(TAG, "Key gesture event handler for pid " + mPid + " died.");
}
- onKeyGestureHandlerDied(mPid);
+ onKeyGestureHandlerRemoved(mPid);
}
- public boolean handleKeyGesture(AidlKeyGestureEvent event, IBinder focusedToken) {
+ public void handleKeyGesture(AidlKeyGestureEvent event, IBinder focusedToken) {
try {
- return mKeyGestureHandler.handleKeyGesture(event, focusedToken);
+ mKeyGestureHandler.handleKeyGesture(event, focusedToken);
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to send key gesture to process " + mPid
+ ", assuming it died.", ex);
binderDied();
}
- return false;
}
}
@@ -1479,18 +1497,21 @@ final class KeyGestureController {
}
}
ipw.println("}");
- ipw.print("mKeyGestureHandlerRecords = {");
synchronized (mKeyGestureHandlerRecords) {
- int i = mKeyGestureHandlerRecords.size() - 1;
- for (int processId : mKeyGestureHandlerRecords.keySet()) {
- ipw.print(processId);
- if (i > 0) {
+ ipw.print("mKeyGestureHandlerRecords = {");
+ int size = mKeyGestureHandlerRecords.size();
+ for (int i = 0; i < size; i++) {
+ int pid = mKeyGestureHandlerRecords.keyAt(i);
+ ipw.print(pid);
+ if (i < size - 1) {
ipw.print(", ");
}
- i--;
}
+ ipw.println("}");
+ ipw.println("mSupportedKeyGestures = " + Arrays.toString(
+ mSupportedKeyGestureToPidMap.copyKeys()));
}
- ipw.println("}");
+
ipw.decreaseIndent();
ipw.println("Last handled KeyGestureEvents: ");
ipw.increaseIndent();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index fde9165a84c6..2066dbc87a0d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1826,8 +1826,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
@NonNull UserData userData) {
final int userId = userData.mUserId;
if (userData.mCurClient == client) {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
+ if (Flags.refactorInsetsController()) {
+ final var statsToken = createStatsTokenForFocusedClient(false /* show */,
+ SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ } else {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
+ }
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
final var userBindingController = userData.mBindingController;
@@ -2097,8 +2103,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
if (visibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
+ if (Flags.refactorInsetsController()) {
+ final var statsToken = createStatsTokenForFocusedClient(false /* show */,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ } else {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
+ }
return InputBindResult.NO_IME;
}
@@ -3855,8 +3867,17 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId);
+
+ if (Flags.refactorInsetsController()) {
+ final var statsToken = createStatsTokenForFocusedClient(
+ false /* show */, SoftInputShowHideReason.HIDE_INVALID_USER,
+ userId);
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ } else {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER,
+ userId);
+ }
return InputBindResult.INVALID_USER;
}
@@ -4993,7 +5014,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
setImeVisibilityOnFocusedWindowClient(false, userData,
null /* TODO(b/353463205) check statsToken */);
} else {
-
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */, reason, userId);
}
@@ -6688,8 +6708,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false, userData,
- null /* TODO(b329229469) initialize statsToken here? */);
+ final var statsToken = createStatsTokenForFocusedClient(false /* show */,
+ SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
+ setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */,
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 ccb9e3ea5cbe..bbf7732c9596 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubEndpointBroker.java
@@ -33,6 +33,7 @@ import android.hardware.contexthub.MessageDeliveryStatus;
import android.hardware.contexthub.Reason;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppState;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
@@ -48,6 +49,7 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
@@ -182,8 +184,11 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
long expiryMillis = RELIABLE_MESSAGE_DUPLICATE_DETECTION_TIMEOUT.toMillis();
if (nowMillis >= nextEntry.getValue() + expiryMillis) {
iterator.remove();
+ } else {
+ // Safe to break since LinkedHashMap is insertion-ordered, so the next entry
+ // will have a later timestamp and will not be expired.
+ break;
}
- break;
}
return mRxMessageHistoryMap.containsKey(message.getMessageSequenceNumber());
@@ -276,6 +281,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
int sessionId = mEndpointManager.reserveSessionId();
EndpointInfo halEndpointInfo = ContextHubServiceUtil.convertHalEndpointInfo(destination);
+ Log.d(TAG, "openSession: sessionId=" + sessionId);
synchronized (mOpenSessionLock) {
try {
@@ -301,6 +307,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
throw new IllegalArgumentException(
"Unknown session ID in closeSession: id=" + sessionId);
}
+ Log.d(TAG, "closeSession: sessionId=" + sessionId + " reason=" + reason);
mEndpointManager.halCloseEndpointSession(
sessionId, ContextHubServiceUtil.toHalReason(reason));
}
@@ -373,12 +380,43 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
try {
mHubInterface.sendMessageToEndpoint(sessionId, halMessage);
} catch (RemoteException e) {
- Log.w(TAG, "Exception while sending message on session " + sessionId, e);
+ Log.e(
+ TAG,
+ "Exception while sending message on session "
+ + sessionId
+ + ", closing session",
+ e);
+ notifySessionClosedToBoth(sessionId, Reason.UNSPECIFIED);
}
} else {
+ IContextHubTransactionCallback wrappedCallback =
+ new IContextHubTransactionCallback.Stub() {
+ @Override
+ public void onQueryResponse(int result, List<NanoAppState> appStates)
+ throws RemoteException {
+ Log.w(TAG, "Unexpected onQueryResponse callback");
+ }
+
+ @Override
+ public void onTransactionComplete(int result) throws RemoteException {
+ callback.onTransactionComplete(result);
+ if (result != ContextHubTransaction.RESULT_SUCCESS) {
+ Log.e(
+ TAG,
+ "Failed to send reliable message "
+ + message
+ + ", closing session");
+ notifySessionClosedToBoth(sessionId, Reason.UNSPECIFIED);
+ }
+ }
+ };
ContextHubServiceTransaction transaction =
mTransactionManager.createSessionMessageTransaction(
- mHubInterface, sessionId, halMessage, mPackageName, callback);
+ mHubInterface,
+ sessionId,
+ halMessage,
+ mPackageName,
+ wrappedCallback);
try {
mTransactionManager.addTransaction(transaction);
info.setReliableMessagePending(transaction.getMessageSequenceNumber());
@@ -445,10 +483,7 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
int id = mSessionMap.keyAt(i);
HubEndpointInfo target = mSessionMap.get(id).getRemoteEndpointInfo();
if (!hasEndpointPermissions(target)) {
- mEndpointManager.halCloseEndpointSessionNoThrow(
- id, Reason.PERMISSION_DENIED);
- onCloseEndpointSession(id, Reason.PERMISSION_DENIED);
- // Resource cleanup is done in onCloseEndpointSession
+ notifySessionClosedToBoth(id, Reason.PERMISSION_DENIED);
}
}
}
@@ -532,8 +567,17 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
/* package */ void onMessageReceived(int sessionId, HubMessage message) {
byte errorCode = onMessageReceivedInternal(sessionId, message);
- if (errorCode != ErrorCode.OK && message.isResponseRequired()) {
- sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
+ if (errorCode != ErrorCode.OK) {
+ Log.e(TAG, "Failed to send message to endpoint: " + message + ", closing session");
+ if (message.isResponseRequired()) {
+ sendMessageDeliveryStatus(sessionId, message.getMessageSequenceNumber(), errorCode);
+ } else {
+ notifySessionClosedToBoth(
+ sessionId,
+ (errorCode == ErrorCode.PERMISSION_DENIED)
+ ? Reason.PERMISSION_DENIED
+ : Reason.UNSPECIFIED);
+ }
}
}
@@ -800,4 +844,16 @@ public class ContextHubEndpointBroker extends IContextHubEndpoint.Stub
+ "-0x"
+ Long.toHexString(endpoint.getIdentifier().getEndpoint()));
}
+
+ /**
+ * Notifies to both the HAL and the app that a session has been closed.
+ *
+ * @param sessionId The ID of the session that was closed
+ * @param halReason The HAL reason for closing the session
+ */
+ private void notifySessionClosedToBoth(int sessionId, byte halReason) {
+ Log.d(TAG, "notifySessionClosedToBoth: sessionId=" + sessionId + ", reason=" + halReason);
+ mEndpointManager.halCloseEndpointSessionNoThrow(sessionId, halReason);
+ onCloseEndpointSession(sessionId, halReason);
+ }
}
diff --git a/services/core/java/com/android/server/locksettings/Android.bp b/services/core/java/com/android/server/locksettings/Android.bp
deleted file mode 100644
index 53f1ac668e49..000000000000
--- a/services/core/java/com/android/server/locksettings/Android.bp
+++ /dev/null
@@ -1,11 +0,0 @@
-aconfig_declarations {
- name: "locksettings_flags",
- package: "com.android.server.locksettings",
- container: "system",
- srcs: ["*.aconfig"],
-}
-
-java_aconfig_library {
- name: "locksettings_flags_lib",
- aconfig_declarations: "locksettings_flags",
-}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index 820c0efcc1cf..4b704d01c3b9 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -428,15 +428,11 @@ class RebootEscrowManager {
/** Wrapper function to set error code serialized through handler, */
private void setLoadEscrowDataErrorCode(@RebootEscrowErrorCode int value, Handler handler) {
- if (Flags.waitForInternetRor()) {
- mInjector.post(
- handler,
- () -> {
- mLoadEscrowDataErrorCode = value;
- });
- } else {
- mLoadEscrowDataErrorCode = value;
- }
+ mInjector.post(
+ handler,
+ () -> {
+ mLoadEscrowDataErrorCode = value;
+ });
}
/** Wrapper function to compare and set error code serialized through handler. */
@@ -511,23 +507,17 @@ class RebootEscrowManager {
mWakeLock.acquire(mInjector.getWakeLockTimeoutMillis());
}
- if (Flags.waitForInternetRor()) {
- // Timeout to stop retrying same as the wake lock timeout.
- mInjector.postDelayed(
- retryHandler,
- () -> {
- mRebootEscrowTimedOut = true;
- },
- mInjector.getLoadEscrowTimeoutMillis());
-
- mInjector.post(
- retryHandler,
- () -> loadRebootEscrowDataOnInternet(retryHandler, users, rebootEscrowUsers));
- return;
- }
+ // Timeout to stop retrying same as the wake lock timeout.
+ mInjector.postDelayed(
+ retryHandler,
+ () -> {
+ mRebootEscrowTimedOut = true;
+ },
+ mInjector.getLoadEscrowTimeoutMillis());
- mInjector.post(retryHandler, () -> loadRebootEscrowDataWithRetry(
- retryHandler, 0, users, rebootEscrowUsers));
+ mInjector.post(
+ retryHandler,
+ () -> loadRebootEscrowDataOnInternet(retryHandler, users, rebootEscrowUsers));
}
void scheduleLoadRebootEscrowDataOrFail(
@@ -548,27 +538,13 @@ class RebootEscrowManager {
return;
}
- if (Flags.waitForInternetRor()) {
- if (mRebootEscrowTimedOut) {
- Slog.w(TAG, "Failed to load reboot escrow data within timeout");
- compareAndSetLoadEscrowDataErrorCode(
- ERROR_NONE, ERROR_TIMEOUT_EXHAUSTED, retryHandler);
- } else {
- Slog.w(
- TAG,
- "Failed to load reboot escrow data after " + attemptNumber + " attempts");
- compareAndSetLoadEscrowDataErrorCode(
- ERROR_NONE, ERROR_RETRY_COUNT_EXHAUSTED, retryHandler);
- }
- onGetRebootEscrowKeyFailed(users, attemptNumber, retryHandler);
- return;
- }
-
- Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
- if (mInjector.serverBasedResumeOnReboot() && !mInjector.isNetworkConnected()) {
- mLoadEscrowDataErrorCode = ERROR_NO_NETWORK;
+ if (mRebootEscrowTimedOut) {
+ Slog.w(TAG, "Failed to load reboot escrow data within timeout");
+ compareAndSetLoadEscrowDataErrorCode(ERROR_NONE, ERROR_TIMEOUT_EXHAUSTED, retryHandler);
} else {
- mLoadEscrowDataErrorCode = ERROR_RETRY_COUNT_EXHAUSTED;
+ Slog.w(TAG, "Failed to load reboot escrow data after " + attemptNumber + " attempts");
+ compareAndSetLoadEscrowDataErrorCode(
+ ERROR_NONE, ERROR_RETRY_COUNT_EXHAUSTED, retryHandler);
}
onGetRebootEscrowKeyFailed(users, attemptNumber, retryHandler);
}
diff --git a/services/core/java/com/android/server/locksettings/flags.aconfig b/services/core/java/com/android/server/locksettings/flags.aconfig
deleted file mode 100644
index 6818de91c98e..000000000000
--- a/services/core/java/com/android/server/locksettings/flags.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.server.locksettings"
-container: "system"
-
-flag {
- name: "wait_for_internet_ror"
- namespace: "sudo"
- description: "Feature flag to wait for internet connectivity before calling resume on reboot server."
- bug: "231660348"
-} \ No newline at end of file
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 58cf29b59961..c174451e8f5b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -192,9 +192,15 @@ public class MediaSessionService extends SystemService implements Monitor {
private final Map<Integer, Set<MediaSessionRecordImpl>> mUserEngagedSessionsForFgs =
new HashMap<>();
- /* Maps uid with all media notifications associated to it */
+ /**
+ * Maps UIDs to their associated media notifications: UID -> (Notification ID ->
+ * {@link android.service.notification.StatusBarNotification}).
+ * Each UID maps to a collection of notifications, identified by their
+ * {@link android.service.notification.StatusBarNotification#getId()}.
+ */
@GuardedBy("mLock")
- private final Map<Integer, Set<StatusBarNotification>> mMediaNotifications = new HashMap<>();
+ private final Map<Integer, Map<String, StatusBarNotification>> mMediaNotifications =
+ new HashMap<>();
// The FullUserRecord of the current users. (i.e. The foreground user that isn't a profile)
// It's always not null after the MediaSessionService is started.
@@ -737,7 +743,8 @@ public class MediaSessionService extends SystemService implements Monitor {
}
synchronized (mLock) {
int uid = mediaSessionRecord.getUid();
- for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid, Set.of())) {
+ for (StatusBarNotification sbn : mMediaNotifications.getOrDefault(uid,
+ Map.of()).values()) {
if (mediaSessionRecord.isLinkedToNotification(sbn.getNotification())) {
setFgsActiveLocked(mediaSessionRecord, sbn);
return;
@@ -771,7 +778,7 @@ public class MediaSessionService extends SystemService implements Monitor {
int uid, MediaSessionRecordImpl record) {
synchronized (mLock) {
for (StatusBarNotification sbn :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
+ mMediaNotifications.getOrDefault(uid, Map.of()).values()) {
if (record.isLinkedToNotification(sbn.getNotification())) {
return sbn;
}
@@ -794,7 +801,8 @@ public class MediaSessionService extends SystemService implements Monitor {
for (MediaSessionRecordImpl record :
mUserEngagedSessionsForFgs.getOrDefault(uid, Set.of())) {
for (StatusBarNotification sbn :
- mMediaNotifications.getOrDefault(uid, Set.of())) {
+ mMediaNotifications.getOrDefault(uid, Map.of()).values()) {
+ //
if (record.isLinkedToNotification(sbn.getNotification())) {
// A user engaged session linked with a media notification is found.
// We shouldn't call stop FGS in this case.
@@ -3262,8 +3270,12 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- mMediaNotifications.putIfAbsent(uid, new HashSet<>());
- mMediaNotifications.get(uid).add(sbn);
+ Map<String, StatusBarNotification> notifications = mMediaNotifications.get(uid);
+ if (notifications == null) {
+ notifications = new HashMap<>();
+ mMediaNotifications.put(uid, notifications);
+ }
+ notifications.put(sbn.getKey(), sbn);
MediaSessionRecordImpl userEngagedRecord =
getUserEngagedMediaSessionRecordForNotification(uid, postedNotification);
if (userEngagedRecord != null) {
@@ -3287,10 +3299,10 @@ public class MediaSessionService extends SystemService implements Monitor {
return;
}
synchronized (mLock) {
- Set<StatusBarNotification> uidMediaNotifications = mMediaNotifications.get(uid);
- if (uidMediaNotifications != null) {
- uidMediaNotifications.remove(sbn);
- if (uidMediaNotifications.isEmpty()) {
+ Map<String, StatusBarNotification> notifications = mMediaNotifications.get(uid);
+ if (notifications != null) {
+ notifications.remove(sbn.getKey());
+ if (notifications.isEmpty()) {
mMediaNotifications.remove(uid);
}
}
diff --git a/services/core/java/com/android/server/media/projection/Android.bp b/services/core/java/com/android/server/media/projection/Android.bp
new file mode 100644
index 000000000000..114be7d20d5b
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/Android.bp
@@ -0,0 +1,21 @@
+//
+// 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.
+
+team {
+ name: "trendy_team_media_projection",
+
+ // go/trendy/manage/engineers/6362947212640256
+ trendy_team_id: "6362947212640256",
+}
diff --git a/services/core/java/com/android/server/media/projection/TEST_MAPPING b/services/core/java/com/android/server/media/projection/TEST_MAPPING
index b33097c50002..7061044aaeee 100644
--- a/services/core/java/com/android/server/media/projection/TEST_MAPPING
+++ b/services/core/java/com/android/server/media/projection/TEST_MAPPING
@@ -2,6 +2,34 @@
"presubmit": [
{
"name": "MediaProjectionTests"
+ },
+ {
+ "name": "CtsMediaProjectionTestCases"
+ },
+ {
+ "name": "CtsMediaProjectionSDK33TestCases"
+ },
+ {
+ "name": "CtsMediaProjectionSDK34TestCases"
+ },
+ {
+ "name": "CtsMediaAudioTestCases",
+ "options": [
+ {
+ "include-filter": "android.media.audio.cts.RemoteSubmixTest"
+ },
+ {
+ "include-filter": "android.media.audio.cts.AudioPlaybackCaptureTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsStatsdAtomHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.cts.statsdatom.media.projection.MediaProjectionAtomsTests"
+ }
+ ]
}
]
}
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index cf8b703a2641..05aac5587c2c 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -18,13 +18,20 @@ package com.android.server.media.quality;
import android.content.ContentValues;
import android.database.Cursor;
+import android.hardware.tv.mediaquality.ColorRange;
+import android.hardware.tv.mediaquality.ColorSpace;
+import android.hardware.tv.mediaquality.ColorTemperature;
import android.hardware.tv.mediaquality.DolbyAudioProcessing;
import android.hardware.tv.mediaquality.DtsVirtualX;
+import android.hardware.tv.mediaquality.Gamma;
import android.hardware.tv.mediaquality.ParameterDefaultValue;
import android.hardware.tv.mediaquality.ParameterName;
import android.hardware.tv.mediaquality.ParameterRange;
import android.hardware.tv.mediaquality.PictureParameter;
+import android.hardware.tv.mediaquality.PictureQualityEventType;
+import android.hardware.tv.mediaquality.QualityLevel;
import android.hardware.tv.mediaquality.SoundParameter;
+import android.media.quality.MediaQualityContract;
import android.media.quality.MediaQualityContract.BaseParameters;
import android.media.quality.MediaQualityContract.PictureQuality;
import android.media.quality.MediaQualityContract.SoundQuality;
@@ -371,7 +378,7 @@ public final class MediaQualityUtils {
}
List<PictureParameter> pictureParams = new ArrayList<>();
if (params.containsKey(PictureQuality.PARAMETER_BRIGHTNESS)) {
- pictureParams.add(PictureParameter.brightness(params.getLong(
+ pictureParams.add(PictureParameter.brightness((float) params.getDouble(
PictureQuality.PARAMETER_BRIGHTNESS)));
params.remove(PictureQuality.PARAMETER_BRIGHTNESS);
}
@@ -441,28 +448,46 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
}
if (params.containsKey(PictureQuality.PARAMETER_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.noiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_NOISE_REDUCTION)));
+ String noiseReductionString = params.getString(
+ PictureQuality.PARAMETER_NOISE_REDUCTION);
+ if (noiseReductionString != null) {
+ byte noiseReductionByte = mapQualityLevel(noiseReductionString);
+ pictureParams.add(PictureParameter.noiseReduction(noiseReductionByte));
+ }
params.remove(PictureQuality.PARAMETER_NOISE_REDUCTION);
}
if (params.containsKey(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)) {
- pictureParams.add(PictureParameter.mpegNoiseReduction(
- (byte) params.getInt(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION)));
+ String mpegNoiseReductionString = params.getString(
+ PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
+ if (mpegNoiseReductionString != null) {
+ byte mpegNoiseReductionByte = mapQualityLevel(mpegNoiseReductionString);
+ pictureParams.add(PictureParameter.mpegNoiseReduction(mpegNoiseReductionByte));
+ }
params.remove(PictureQuality.PARAMETER_MPEG_NOISE_REDUCTION);
}
if (params.containsKey(PictureQuality.PARAMETER_FLESH_TONE)) {
- pictureParams.add(PictureParameter.fleshTone(
- (byte) params.getInt(PictureQuality.PARAMETER_FLESH_TONE)));
+ String fleshToneString = params.getString(PictureQuality.PARAMETER_FLESH_TONE);
+ if (fleshToneString != null) {
+ byte fleshToneByte = mapQualityLevel(fleshToneString);
+ pictureParams.add(PictureParameter.fleshTone(fleshToneByte));
+ }
params.remove(PictureQuality.PARAMETER_FLESH_TONE);
}
if (params.containsKey(PictureQuality.PARAMETER_DECONTOUR)) {
- pictureParams.add(PictureParameter.deContour(
- (byte) params.getInt(PictureQuality.PARAMETER_DECONTOUR)));
+ String decontourString = params.getString(PictureQuality.PARAMETER_DECONTOUR);
+ if (decontourString != null) {
+ byte decontourByte = mapQualityLevel(decontourString);
+ pictureParams.add(PictureParameter.deContour(decontourByte));
+ }
params.remove(PictureQuality.PARAMETER_DECONTOUR);
}
if (params.containsKey(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)) {
- pictureParams.add(PictureParameter.dynamicLumaControl(
- (byte) params.getInt(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL)));
+ String dynamicLunaControlString = params.getString(
+ PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
+ if (dynamicLunaControlString != null) {
+ byte dynamicLunaControlByte = mapQualityLevel(dynamicLunaControlString);
+ pictureParams.add(PictureParameter.dynamicLumaControl(dynamicLunaControlByte));
+ }
params.remove(PictureQuality.PARAMETER_DYNAMIC_LUMA_CONTROL);
}
if (params.containsKey(PictureQuality.PARAMETER_FILM_MODE)) {
@@ -481,9 +506,48 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_COLOR_TUNE);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE)) {
- pictureParams.add(PictureParameter.colorTemperature(
- (byte) params.getInt(
- PictureQuality.PARAMETER_COLOR_TEMPERATURE)));
+ String colorTemperatureString = params.getString(
+ PictureQuality.PARAMETER_COLOR_TEMPERATURE);
+ if (colorTemperatureString != null) {
+ byte colorTemperatureByte;
+ switch (colorTemperatureString) {
+ case MediaQualityContract.COLOR_TEMP_USER:
+ colorTemperatureByte = ColorTemperature.USER;
+ break;
+ case MediaQualityContract.COLOR_TEMP_COOL:
+ colorTemperatureByte = ColorTemperature.COOL;
+ break;
+ case MediaQualityContract.COLOR_TEMP_STANDARD:
+ colorTemperatureByte = ColorTemperature.STANDARD;
+ break;
+ case MediaQualityContract.COLOR_TEMP_WARM:
+ colorTemperatureByte = ColorTemperature.WARM;
+ break;
+ case MediaQualityContract.COLOR_TEMP_USER_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.USER_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_COOL_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.COOL_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_STANDARD_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.STANDARD_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_WARM_HDR10PLUS:
+ colorTemperatureByte = ColorTemperature.WARM_HDR10PLUS;
+ break;
+ case MediaQualityContract.COLOR_TEMP_FMMSDR:
+ colorTemperatureByte = ColorTemperature.FMMSDR;
+ break;
+ case MediaQualityContract.COLOR_TEMP_FMMHDR:
+ colorTemperatureByte = ColorTemperature.FMMHDR;
+ break;
+ default:
+ colorTemperatureByte = ColorTemperature.STANDARD;
+ Log.e("PictureParams", "Invalid color_temp string: "
+ + colorTemperatureString);
+ }
+ pictureParams.add(PictureParameter.colorTemperature(colorTemperatureByte));
+ }
params.remove(PictureQuality.PARAMETER_COLOR_TEMPERATURE);
}
if (params.containsKey(PictureQuality.PARAMETER_GLOBAL_DIMMING)) {
@@ -517,8 +581,26 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_BLUE_GAIN);
}
if (params.containsKey(PictureQuality.PARAMETER_LEVEL_RANGE)) {
- pictureParams.add(PictureParameter.levelRange(
- (byte) params.getInt(PictureQuality.PARAMETER_LEVEL_RANGE)));
+ String levelRangeString = params.getString(PictureQuality.PARAMETER_LEVEL_RANGE);
+ if (levelRangeString != null) {
+ byte levelRangeByte;
+ switch (levelRangeString) {
+ case "AUTO":
+ levelRangeByte = ColorRange.AUTO;
+ break;
+ case "LIMITED":
+ levelRangeByte = ColorRange.LIMITED;
+ break;
+ case "FULL":
+ levelRangeByte = ColorRange.FULL;
+ break;
+ default:
+ levelRangeByte = ColorRange.AUTO;
+ Log.e("PictureParams", "Invalid color_range string: "
+ + levelRangeString);
+ }
+ pictureParams.add(PictureParameter.levelRange(levelRangeByte));
+ }
params.remove(PictureQuality.PARAMETER_LEVEL_RANGE);
}
if (params.containsKey(PictureQuality.PARAMETER_GAMUT_MAPPING)) {
@@ -547,13 +629,61 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_CVRR);
}
if (params.containsKey(PictureQuality.PARAMETER_HDMI_RGB_RANGE)) {
- pictureParams.add(PictureParameter.hdmiRgbRange(
- (byte) params.getInt(PictureQuality.PARAMETER_HDMI_RGB_RANGE)));
+ String hdmiRgbRangeString = params.getString(PictureQuality.PARAMETER_HDMI_RGB_RANGE);
+ if (hdmiRgbRangeString != null) {
+ byte hdmiRgbRangeByte;
+ switch (hdmiRgbRangeString) {
+ case "AUTO":
+ hdmiRgbRangeByte = ColorRange.AUTO;
+ break;
+ case "LIMITED":
+ hdmiRgbRangeByte = ColorRange.LIMITED;
+ break;
+ case "FULL":
+ hdmiRgbRangeByte = ColorRange.FULL;
+ break;
+ default:
+ hdmiRgbRangeByte = ColorRange.AUTO;
+ Log.e("PictureParams", "Invalid hdmi_rgb_range string: "
+ + hdmiRgbRangeByte);
+ }
+ pictureParams.add(PictureParameter.hdmiRgbRange(hdmiRgbRangeByte));
+ }
params.remove(PictureQuality.PARAMETER_HDMI_RGB_RANGE);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_SPACE)) {
- pictureParams.add(PictureParameter.colorSpace(
- (byte) params.getInt(PictureQuality.PARAMETER_COLOR_SPACE)));
+ String colorSpaceString = params.getString(PictureQuality.PARAMETER_COLOR_SPACE);
+ if (colorSpaceString != null) {
+ byte colorSpaceByte;
+ switch (colorSpaceString) {
+ case "AUTO":
+ colorSpaceByte = ColorSpace.AUTO;
+ break;
+ case "S_RGB_BT_709":
+ colorSpaceByte = ColorSpace.S_RGB_BT_709;
+ break;
+ case "DCI":
+ colorSpaceByte = ColorSpace.DCI;
+ break;
+ case "ADOBE_RGB":
+ colorSpaceByte = ColorSpace.ADOBE_RGB;
+ break;
+ case "BT2020":
+ colorSpaceByte = ColorSpace.BT2020;
+ break;
+ case "ON":
+ colorSpaceByte = ColorSpace.ON;
+ break;
+ case "OFF":
+ colorSpaceByte = ColorSpace.OFF;
+ break;
+ default:
+ colorSpaceByte = ColorSpace.OFF;
+ Log.e("PictureParams", "Invalid color_space string: "
+ + colorSpaceString);
+ }
+ pictureParams.add(PictureParameter.colorSpace(colorSpaceByte));
+ }
params.remove(PictureQuality.PARAMETER_COLOR_SPACE);
}
if (params.containsKey(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_NITS)) {
@@ -567,8 +697,25 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_PANEL_INIT_MAX_LUMINCE_VALID);
}
if (params.containsKey(PictureQuality.PARAMETER_GAMMA)) {
- pictureParams.add(PictureParameter.gamma(
- (byte) params.getInt(PictureQuality.PARAMETER_GAMMA)));
+ String gammaString = params.getString(PictureQuality.PARAMETER_GAMMA);
+ if (gammaString != null) {
+ byte gammaByte;
+ switch (gammaString) {
+ case "DARK":
+ gammaByte = Gamma.DARK;
+ break;
+ case "MIDDLE":
+ gammaByte = Gamma.MIDDLE;
+ break;
+ case "BRIGHT":
+ gammaByte = Gamma.BRIGHT;
+ break;
+ default:
+ gammaByte = Gamma.DARK;
+ Log.e("PictureParams", "Invalid gamma string: " + gammaString);
+ }
+ pictureParams.add(PictureParameter.gamma(gammaByte));
+ }
params.remove(PictureQuality.PARAMETER_GAMMA);
}
if (params.containsKey(PictureQuality.PARAMETER_COLOR_TEMPERATURE_RED_OFFSET)) {
@@ -602,13 +749,19 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_ELEVEN_POINT_BLUE);
}
if (params.containsKey(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)) {
- pictureParams.add(PictureParameter.lowBlueLight(
- (byte) params.getInt(PictureQuality.PARAMETER_LOW_BLUE_LIGHT)));
+ String lowBlueLightString = params.getString(PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
+ if (lowBlueLightString != null) {
+ byte lowBlueLightByte = mapQualityLevel(lowBlueLightString);
+ pictureParams.add(PictureParameter.lowBlueLight(lowBlueLightByte));
+ }
params.remove(PictureQuality.PARAMETER_LOW_BLUE_LIGHT);
}
if (params.containsKey(PictureQuality.PARAMETER_LD_MODE)) {
- pictureParams.add(PictureParameter.LdMode(
- (byte) params.getInt(PictureQuality.PARAMETER_LD_MODE)));
+ String ldModeString = params.getString(PictureQuality.PARAMETER_LD_MODE);
+ if (ldModeString != null) {
+ byte ldModeByte = mapQualityLevel(ldModeString);
+ pictureParams.add(PictureParameter.LdMode(ldModeByte));
+ }
params.remove(PictureQuality.PARAMETER_LD_MODE);
}
if (params.containsKey(PictureQuality.PARAMETER_OSD_RED_GAIN)) {
@@ -767,8 +920,44 @@ public final class MediaQualityUtils {
params.remove(PictureQuality.PARAMETER_COLOR_TUNER_LUMINANCE_FLESH);
}
if (params.containsKey(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)) {
- pictureParams.add(PictureParameter.pictureQualityEventType(
- (byte) params.getInt(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE)));
+ String pictureQualityEventTypeString = params.getString(
+ PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE);
+ if (pictureQualityEventTypeString != null) {
+ byte pictureQualityEventTypeByte;
+ switch (pictureQualityEventTypeString) {
+ case "NONE":
+ pictureQualityEventTypeByte = PictureQualityEventType.NONE;
+ break;
+ case "BBD_RESULT":
+ pictureQualityEventTypeByte = PictureQualityEventType.BBD_RESULT;
+ break;
+ case "VIDEO_DELAY_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.VIDEO_DELAY_CHANGE;
+ break;
+ case "CAPTUREPOINT_INFO_CHANGE":
+ pictureQualityEventTypeByte =
+ PictureQualityEventType.CAPTUREPOINT_INFO_CHANGE;
+ break;
+ case "VIDEOPATH_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.VIDEOPATH_CHANGE;
+ break;
+ case "EXTRA_FRAME_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.EXTRA_FRAME_CHANGE;
+ break;
+ case "DOLBY_IQ_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.DOLBY_IQ_CHANGE;
+ break;
+ case "DOLBY_APO_CHANGE":
+ pictureQualityEventTypeByte = PictureQualityEventType.DOLBY_APO_CHANGE;
+ break;
+ default:
+ pictureQualityEventTypeByte = PictureQualityEventType.NONE;
+ Log.e("PictureParams", "Invalid event type string: "
+ + pictureQualityEventTypeString);
+ }
+ pictureParams.add(
+ PictureParameter.pictureQualityEventType(pictureQualityEventTypeByte));
+ }
params.remove(PictureQuality.PARAMETER_PICTURE_QUALITY_EVENT_TYPE);
}
return pictureParams.toArray(new PictureParameter[0]);
@@ -1657,6 +1846,19 @@ public final class MediaQualityUtils {
return colIndex != -1 ? cursor.getString(colIndex) : null;
}
+ private static byte mapQualityLevel(String qualityLevel) {
+ return switch (qualityLevel) {
+ case MediaQualityContract.LEVEL_OFF -> QualityLevel.OFF;
+ case MediaQualityContract.LEVEL_LOW -> QualityLevel.LOW;
+ case MediaQualityContract.LEVEL_MEDIUM -> QualityLevel.MEDIUM;
+ case MediaQualityContract.LEVEL_HIGH -> QualityLevel.HIGH;
+ default -> {
+ Log.e("PictureParams", "Invalid noise_reduction string: " + qualityLevel);
+ yield QualityLevel.OFF;
+ }
+ };
+ }
+
private MediaQualityUtils() {
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8d787fe99e3a..651111e431c3 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -985,8 +985,8 @@ public final class OverlayManagerService extends SystemService {
final String pkgName = request.overlay.getPackageName();
if (callingUid != Process.ROOT_UID && !ArrayUtils.contains(
mPackageManager.getPackagesForUid(callingUid), pkgName)) {
- throw new IllegalArgumentException("UID " + callingUid + " does own package"
- + "name " + pkgName);
+ throw new IllegalArgumentException("UID " + callingUid + " does not own "
+ + "packageName " + pkgName);
}
} else {
// Enforce actor requirements for enabling, disabling, and reordering overlays.
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b441e9dd561d..7e5ada54c953 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -1994,6 +1994,9 @@ public class ComputerEngine implements Computer {
if (Process.isSdkSandboxUid(uid)) {
uid = getBaseSdkSandboxUid();
}
+ if(isKnownIsolatedComputeApp(uid)) {
+ uid = getIsolatedOwner(uid);
+ }
final int appId = UserHandle.getAppId(uid);
return getPackagesForUidInternalBody(callingUid, userId, appId, isCallerInstantApp);
}
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 38aa57f785e5..bbee77ce58ad 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -425,7 +425,7 @@ final class DeletePackageHelper {
user == null || user.getIdentifier() == USER_ALL;
if ((!deleteSystem || deleteAllUsers) && disabledPs == null) {
Slog.w(TAG, "Attempt to delete unknown system package "
- + ps.getPkg().getPackageName());
+ + ps.getName());
return null;
}
// Confirmed if the system package has been updated
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index acdc79fb9922..e02ec6a9e3b4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3077,7 +3077,8 @@ final class InstallPackageHelper {
}
if (succeeded) {
- Slog.i(TAG, "installation completed:" + packageName);
+ Slog.i(TAG, "installation completed for package:" + packageName
+ + ". Final code path: " + pkgSetting.getPath().getPath());
if (Flags.aslInApkAppMetadataSource()
&& pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 3361dbc2df07..72f2068800d2 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -869,6 +869,9 @@ final class InstallRequest {
public void setScannedPackageSettingFirstInstallTimeFromReplaced(
@Nullable PackageStateInternal replacedPkgSetting, int[] userId) {
assertScanResultExists();
+ if (replacedPkgSetting == null) {
+ return;
+ }
mScanResult.mPkgSetting.setFirstInstallTimeFromReplaced(replacedPkgSetting, userId);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index f88681dbcaeb..e98176b0e82b 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -18,6 +18,7 @@ package com.android.server.pm;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SpecialUsers.CanBeNULL;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.LauncherUserInfo;
@@ -620,11 +621,17 @@ public abstract class UserManagerInternal {
* Returns the user id of the communal profile, or {@link android.os.UserHandle#USER_NULL}
* if there is no such user.
*/
- public abstract @UserIdInt int getCommunalProfileId();
+ public abstract @CanBeNULL @UserIdInt int getCommunalProfileId();
/**
- * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from
- * background users.
+ * Returns the user id of the supervising profile, or {@link android.os.UserHandle#USER_NULL} if
+ * there is no such user.
+ */
+ public abstract @CanBeNULL @UserIdInt int getSupervisingProfileId();
+
+ /**
+ * Checks whether to show a notification for sounds (e.g., alarms, timers, etc.) from background
+ * users.
*/
public static boolean shouldShowNotificationForBackgroundUserSounds() {
return Flags.addUiForSoundsFromBackgroundUsers() && Resources.getSystem().getBoolean(
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 33a7e7476cf6..053b4aae90dd 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1530,6 +1530,20 @@ public class UserManagerService extends IUserManager.Stub {
return UserHandle.USER_NULL;
}
+ /** Returns the currently-designated supervising profile, or USER_NULL if not present. */
+ private @CanBeNULL @UserIdInt int getSupervisingProfileId() {
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserInfo user = mUsers.valueAt(i).info;
+ if (user.isSupervisingProfile() && !mRemovingUserIds.get(user.id)) {
+ return user.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
public @NonNull List<UserInfo> getUsers(boolean excludeDying) {
return getUsers(/*excludePartial= */ true, excludeDying, /* excludePreCreated= */
true);
@@ -8348,10 +8362,14 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
- public @UserIdInt int getCommunalProfileId() {
+ public @CanBeNULL @UserIdInt int getCommunalProfileId() {
return getCommunalProfileIdUnchecked();
}
+ @Override
+ public @CanBeNULL @UserIdInt int getSupervisingProfileId() {
+ return UserManagerService.this.getSupervisingProfileId();
+ }
} // class LocalService
diff --git a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
index e989d6875d15..4c14e96e6c98 100644
--- a/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
+++ b/services/core/java/com/android/server/pm/permission/AccessCheckDelegate.java
@@ -36,6 +36,7 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.SparseArray;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HexConsumer;
@@ -141,150 +142,191 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
class AccessCheckDelegateImpl implements AccessCheckDelegate {
public static final String SHELL_PKG = "com.android.shell";
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
private int mDelegateAndOwnerUid = INVALID_UID;
@Nullable
+ @GuardedBy("mLock")
private String mDelegatePackage;
@Nullable
+ @GuardedBy("mLock")
private String[] mDelegatePermissions;
+ @GuardedBy("mLock")
boolean mDelegateAllPermissions;
@Nullable
+ @GuardedBy("mLock")
private SparseArray<ArrayMap<String, Integer>> mOverridePermissionStates;
@Override
public void setShellPermissionDelegate(int uid, @NonNull String packageName,
@Nullable String[] permissions) {
- mDelegateAndOwnerUid = uid;
- mDelegatePackage = packageName;
- mDelegatePermissions = permissions;
- mDelegateAllPermissions = permissions == null;
+ synchronized (mLock) {
+ mDelegateAndOwnerUid = uid;
+ mDelegatePackage = packageName;
+ mDelegatePermissions = permissions;
+ mDelegateAllPermissions = permissions == null;
+ }
PackageManager.invalidatePackageInfoCache();
}
@Override
public void removeShellPermissionDelegate() {
- mDelegatePackage = null;
- mDelegatePermissions = null;
- mDelegateAllPermissions = false;
+ synchronized (mLock) {
+ mDelegatePackage = null;
+ mDelegatePermissions = null;
+ mDelegateAllPermissions = false;
+ }
PackageManager.invalidatePackageInfoCache();
}
@Override
public void addOverridePermissionState(int ownerUid, int uid, @NonNull String permission,
int state) {
- if (mOverridePermissionStates == null) {
- mDelegateAndOwnerUid = ownerUid;
- mOverridePermissionStates = new SparseArray<>();
- }
+ synchronized (mLock) {
+ if (mOverridePermissionStates == null) {
+ mDelegateAndOwnerUid = ownerUid;
+ mOverridePermissionStates = new SparseArray<>();
+ }
- int uidIdx = mOverridePermissionStates.indexOfKey(uid);
- ArrayMap<String, Integer> perUidOverrides;
- if (uidIdx < 0) {
- perUidOverrides = new ArrayMap<>();
- mOverridePermissionStates.put(uid, perUidOverrides);
- } else {
- perUidOverrides = mOverridePermissionStates.valueAt(uidIdx);
- }
+ int uidIdx = mOverridePermissionStates.indexOfKey(uid);
+ ArrayMap<String, Integer> perUidOverrides;
+ if (uidIdx < 0) {
+ perUidOverrides = new ArrayMap<>();
+ mOverridePermissionStates.put(uid, perUidOverrides);
+ } else {
+ perUidOverrides = mOverridePermissionStates.valueAt(uidIdx);
+ }
- perUidOverrides.put(permission, state);
+ perUidOverrides.put(permission, state);
+ }
PackageManager.invalidatePackageInfoCache();
}
@Override
public void removeOverridePermissionState(int uid, @NonNull String permission) {
- if (mOverridePermissionStates == null) {
- return;
- }
+ synchronized (mLock) {
+ if (mOverridePermissionStates == null) {
+ return;
+ }
- ArrayMap<String, Integer> perUidOverrides = mOverridePermissionStates.get(uid);
+ ArrayMap<String, Integer> perUidOverrides = mOverridePermissionStates.get(uid);
- if (perUidOverrides == null) {
- return;
- }
+ if (perUidOverrides == null) {
+ return;
+ }
- perUidOverrides.remove(permission);
- PackageManager.invalidatePackageInfoCache();
+ perUidOverrides.remove(permission);
- if (perUidOverrides.isEmpty()) {
- mOverridePermissionStates.remove(uid);
- }
- if (mOverridePermissionStates.size() == 0) {
- mOverridePermissionStates = null;
+ if (perUidOverrides.isEmpty()) {
+ mOverridePermissionStates.remove(uid);
+ }
+ if (mOverridePermissionStates.size() == 0) {
+ mOverridePermissionStates = null;
+ }
}
+ PackageManager.invalidatePackageInfoCache();
}
@Override
public void clearOverridePermissionStates(int uid) {
- if (mOverridePermissionStates == null) {
- return;
- }
+ synchronized (mLock) {
+ if (mOverridePermissionStates == null) {
+ return;
+ }
- mOverridePermissionStates.remove(uid);
- PackageManager.invalidatePackageInfoCache();
+ mOverridePermissionStates.remove(uid);
- if (mOverridePermissionStates.size() == 0) {
- mOverridePermissionStates = null;
+ if (mOverridePermissionStates.size() == 0) {
+ mOverridePermissionStates = null;
+ }
}
+ PackageManager.invalidatePackageInfoCache();
}
@Override
public void clearAllOverridePermissionStates() {
- mOverridePermissionStates = null;
+ synchronized (mLock) {
+ mOverridePermissionStates = null;
+ }
PackageManager.invalidatePackageInfoCache();
}
@Override
public List<String> getDelegatedPermissionNames() {
- return mDelegatePermissions == null ? null : List.of(mDelegatePermissions);
+ synchronized (mLock) {
+ return mDelegatePermissions == null ? null : List.of(mDelegatePermissions);
+ }
}
@Override
public boolean hasShellPermissionDelegate() {
- return mDelegateAllPermissions || mDelegatePermissions != null;
+ synchronized (mLock) {
+ return mDelegateAllPermissions || mDelegatePermissions != null;
+ }
}
@Override
public boolean isDelegatePackage(int uid, @NonNull String packageName) {
- return mDelegateAndOwnerUid == uid && TextUtils.equals(mDelegatePackage, packageName);
+ synchronized (mLock) {
+ return mDelegateAndOwnerUid == uid
+ && TextUtils.equals(mDelegatePackage, packageName);
+ }
}
@Override
public boolean hasOverriddenPermissions() {
- return mOverridePermissionStates != null;
+ synchronized (mLock) {
+ return mOverridePermissionStates != null;
+ }
}
@Override
public boolean isDelegateAndOwnerUid(int uid) {
- return uid == mDelegateAndOwnerUid;
+ synchronized (mLock) {
+ return uid == mDelegateAndOwnerUid;
+ }
}
@Override
public boolean hasDelegateOrOverrides() {
- return hasShellPermissionDelegate() || hasOverriddenPermissions();
+ synchronized (mLock) {
+ return hasShellPermissionDelegate() || hasOverriddenPermissions();
+ }
}
@Override
public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
@NonNull String persistentDeviceId, @UserIdInt int userId,
@NonNull QuadFunction<String, String, String, Integer, Integer> superImpl) {
- if (TextUtils.equals(mDelegatePackage, packageName) && !SHELL_PKG.equals(packageName)) {
- if (isDelegatePermission(permissionName)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return checkPermission(SHELL_PKG, permissionName, persistentDeviceId,
- userId, superImpl);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = !SHELL_PKG.equals(packageName)
+ && TextUtils.equals(mDelegatePackage, packageName)
+ && isDelegatePermission(permissionName);
+
+ if (!useShellDelegate && mOverridePermissionStates != null) {
+ int uid = LocalServices.getService(PackageManagerInternal.class)
+ .getPackageUid(packageName, 0, userId);
+ if (uid >= 0) {
+ Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
+ if (permissionGrants != null
+ && permissionGrants.containsKey(permissionName)) {
+ return permissionGrants.get(permissionName);
+ }
}
}
}
- if (mOverridePermissionStates != null) {
- int uid = LocalServices.getService(PackageManagerInternal.class)
- .getPackageUid(packageName, 0, userId);
- if (uid >= 0) {
- Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
- if (permissionGrants != null && permissionGrants.containsKey(permissionName)) {
- return permissionGrants.get(permissionName);
- }
+
+ if (useShellDelegate) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return checkPermission(SHELL_PKG, permissionName, persistentDeviceId, userId,
+ superImpl);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
return superImpl.apply(packageName, permissionName, persistentDeviceId, userId);
@@ -294,21 +336,27 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
public int checkUidPermission(int uid, @NonNull String permissionName,
@NonNull String persistentDeviceId,
@NonNull TriFunction<Integer, String, String, Integer> superImpl) {
- if (uid == mDelegateAndOwnerUid && uid != Process.SHELL_UID) {
- if (isDelegatePermission(permissionName)) {
- final long identity = Binder.clearCallingIdentity();
- try {
- return checkUidPermission(Process.SHELL_UID, permissionName,
- persistentDeviceId, superImpl);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid != Process.SHELL_UID && uid == mDelegateAndOwnerUid
+ && isDelegatePermission(permissionName);
+
+ if (!useShellDelegate && mOverridePermissionStates != null) {
+ Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
+ if (permissionGrants != null && permissionGrants.containsKey(permissionName)) {
+ return permissionGrants.get(permissionName);
}
}
}
- if (mOverridePermissionStates != null) {
- Map<String, Integer> permissionGrants = mOverridePermissionStates.get(uid);
- if (permissionGrants != null && permissionGrants.containsKey(permissionName)) {
- return permissionGrants.get(permissionName);
+
+ if (useShellDelegate) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return checkUidPermission(Process.SHELL_UID, permissionName, persistentDeviceId,
+ superImpl);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
}
return superImpl.apply(uid, permissionName, persistentDeviceId);
@@ -319,7 +367,13 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Nullable String attributionTag, int virtualDeviceId, boolean raw,
@NonNull HexFunction<Integer, Integer, String, String, Integer, Boolean, Integer>
superImpl) {
- if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid == mDelegateAndOwnerUid && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -335,7 +389,13 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Override
public int checkAudioOperation(int code, int usage, int uid, @Nullable String packageName,
@NonNull QuadFunction<Integer, Integer, Integer, String, Integer> superImpl) {
- if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid == mDelegateAndOwnerUid && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -354,7 +414,13 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Nullable String message, boolean shouldCollectMessage, int notedCount,
@NonNull NonaFunction<Integer, Integer, String, String, Integer, Boolean, String,
Boolean, Integer, SyncNotedAppOp> superImpl) {
- if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid == mDelegateAndOwnerUid && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -375,21 +441,29 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@Nullable String message, boolean shouldCollectMessage, boolean skiProxyOperation,
@NonNull HexFunction<Integer, AttributionSource, Boolean, String, Boolean,
Boolean, SyncNotedAppOp> superImpl) {
- if (!isDelegateOp(code)) {
- return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp,
- message, shouldCollectMessage, skiProxyOperation);
+ boolean isDelegateOp;
+ int delegateAndOwnerUid;
+
+ synchronized (mLock) {
+ isDelegateOp = isDelegateOp(code);
+ delegateAndOwnerUid = mDelegateAndOwnerUid;
+ }
+
+ if (!isDelegateOp) {
+ return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skiProxyOperation);
}
final int shellUid = UserHandle.getUid(
UserHandle.getUserId(attributionSource.getUid()), Process.SHELL_UID);
AttributionSource next = attributionSource.getNext();
- if (next != null && next.getUid() == mDelegateAndOwnerUid) {
+ if (next != null && next.getUid() == delegateAndOwnerUid) {
next = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
next.getAttributionTag(), next.getToken(), /*renouncedPermissions*/ null,
next.getDeviceId(), next.getNext());
attributionSource = new AttributionSource(attributionSource, next);
}
- if (attributionSource.getUid() == mDelegateAndOwnerUid) {
+ if (attributionSource.getUid() == delegateAndOwnerUid) {
attributionSource = new AttributionSource(shellUid, Process.INVALID_PID, SHELL_PKG,
attributionSource.getAttributionTag(),
attributionSource.getToken(), /*renouncedPermissions*/ null,
@@ -397,9 +471,8 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
}
final long identity = Binder.clearCallingIdentity();
try {
- return superImpl.apply(code, attributionSource,
- shouldCollectAsyncNotedOp, message, shouldCollectMessage,
- skiProxyOperation);
+ return superImpl.apply(code, attributionSource, shouldCollectAsyncNotedOp, message,
+ shouldCollectMessage, skiProxyOperation);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -413,7 +486,13 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@AttributionFlags int attributionFlags, int attributionChainId,
@NonNull DodecFunction<IBinder, Integer, Integer, String, String, Integer, Boolean,
Boolean, String, Boolean, Integer, Integer, SyncNotedAppOp> superImpl) {
- if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid == mDelegateAndOwnerUid && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(uid),
Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -440,7 +519,14 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@NonNull UndecFunction<IBinder, Integer, AttributionSource, Boolean,
Boolean, String, Boolean, Boolean, Integer, Integer, Integer,
SyncNotedAppOp> superImpl) {
- if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = attributionSource.getUid() == mDelegateAndOwnerUid
+ && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -467,7 +553,14 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
@NonNull AttributionSource attributionSource, boolean skipProxyOperation,
@NonNull QuadFunction<IBinder, Integer, AttributionSource, Boolean,
Void> superImpl) {
- if (attributionSource.getUid() == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = attributionSource.getUid() == mDelegateAndOwnerUid
+ && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid = UserHandle.getUid(UserHandle.getUserId(
attributionSource.getUid()), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
@@ -490,7 +583,13 @@ public interface AccessCheckDelegate extends CheckPermissionDelegate, CheckOpsDe
public void finishOperation(IBinder clientId, int code, int uid, String packageName,
String attributionTag, int virtualDeviceId, @NonNull HexConsumer<IBinder, Integer,
Integer, String, String, Integer> superImpl) {
- if (uid == mDelegateAndOwnerUid && isDelegateOp(code)) {
+ boolean useShellDelegate;
+
+ synchronized (mLock) {
+ useShellDelegate = uid == mDelegateAndOwnerUid && isDelegateOp(code);
+ }
+
+ if (useShellDelegate) {
final int shellUid =
UserHandle.getUid(UserHandle.getUserId(uid), Process.SHELL_UID);
final long identity = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 38d458767015..e9f522d08328 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -86,6 +86,7 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
+import static com.android.hardware.input.Flags.fixSearchModifierFallbacks;
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
@@ -115,7 +116,6 @@ import static com.android.server.wm.WindowManagerPolicyProto.SCREEN_ON_FULLY;
import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
import android.accessibilityservice.AccessibilityService;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
@@ -268,6 +268,7 @@ import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
@@ -311,6 +312,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
static final int SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP = 8;
+ static final int SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP = 9;
// must match: config_LongPressOnPowerBehavior in config.xml
// The config value can be overridden using Settings.Global.POWER_BUTTON_LONG_PRESS
@@ -1234,8 +1236,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
break;
}
case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
- attemptToDreamFromShortPowerButtonPress(
- true,
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ false,
+ /* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
break;
}
@@ -1269,13 +1273,22 @@ public class PhoneWindowManager implements WindowManagerPolicy {
lockNow(options);
} else {
// If the hub cannot be run, attempt to dream instead.
- attemptToDreamFromShortPowerButtonPress(
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
/* isScreenOn */ true,
+ /* awakeWhenDream */ false,
/* noDreamAction */
() -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
}
break;
}
+ case SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP: {
+ attemptToDreamOrAwakeFromShortPowerButtonPress(
+ /* isScreenOn */ true,
+ /* awakeWhenDream */ true,
+ /* noDreamAction */
+ () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+ break;
+ }
}
}
}
@@ -1319,15 +1332,18 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
/**
- * Attempt to dream from a power button press.
+ * Attempt to dream, awake or sleep from a power button press.
*
* @param isScreenOn Whether the screen is currently on.
+ * @param awakeWhenDream When it's set to {@code true}, awake the device from dreaming.
+ * Otherwise, go to sleep.
* @param noDreamAction The action to perform if dreaming is not possible.
*/
- private void attemptToDreamFromShortPowerButtonPress(
- boolean isScreenOn, Runnable noDreamAction) {
+ private void attemptToDreamOrAwakeFromShortPowerButtonPress(
+ boolean isScreenOn, boolean awakeWhenDream, Runnable noDreamAction) {
if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP
- && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP) {
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP
+ && mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP) {
// If the power button behavior isn't one that should be able to trigger the dream, give
// up.
noDreamAction.run();
@@ -1335,9 +1351,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
- if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
- Slog.d(TAG, "Can't start dreaming when attempting to dream from short power"
- + " press (isScreenOn=" + isScreenOn + ")");
+ if (dreamManagerInternal == null) {
+ Slog.d(TAG,
+ "Can't access dream manager dreaming when attempting to start or stop dream "
+ + "from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
+ noDreamAction.run();
+ return;
+ }
+
+ if (!dreamManagerInternal.canStartDreaming(isScreenOn)) {
+ if (awakeWhenDream && dreamManagerInternal.isDreaming()) {
+ dreamManagerInternal.stopDream(false /*immediate*/, "short press power" /*reason*/);
+ return;
+ }
+ Slog.d(TAG,
+ "Can't start dreaming and the device is not dreaming when attempting to start "
+ + "or stop dream from short power press (isScreenOn="
+ + isScreenOn + ", awakeWhenDream=" + awakeWhenDream + ")");
noDreamAction.run();
return;
}
@@ -2312,6 +2343,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return ActivityManager.getService();
}
+ LockPatternUtils getLockPatternUtils() {
+ return new LockPatternUtils(mContext);
+ }
+
ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
return new ButtonOverridePermissionChecker();
}
@@ -2360,7 +2395,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mAccessibilityShortcutController = injector.getAccessibilityShortcutController(
mContext, new Handler(), mCurrentUserId);
mGlobalActionsFactory = injector.getGlobalActionsFactory();
- mLockPatternUtils = new LockPatternUtils(mContext);
+ mLockPatternUtils = injector.getLockPatternUtils();
mLogger = new MetricsLogger();
Resources res = mContext.getResources();
@@ -4147,6 +4182,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
+ if (fixSearchModifierFallbacks()) {
+ // Pass event as unhandled to give other services, e.g. InputManagerService, the
+ // opportunity to determine if the event can be modified, e.g. generating a fallback for
+ // meta/search events.
+ return false;
+ }
+
// Reserve all the META modifier combos for system behavior
return (metaState & KeyEvent.META_META_ON) != 0;
}
@@ -4240,19 +4282,51 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (!useKeyGestureEventHandler()) {
return;
}
- mInputManager.registerKeyGestureEventHandler((event, focusedToken) -> {
- boolean handled = PhoneWindowManager.this.handleKeyGestureEvent(event,
- focusedToken);
- if (handled && !event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
- (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
- mPowerKeyHandled = true;
- }
- return handled;
- });
+ List<Integer> supportedGestures = new ArrayList<>(List.of(
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD,
+ KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT
+ ));
+ if (enableTalkbackAndMagnifierKeyGestures()) {
+ supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK);
+ }
+ if (enableVoiceAccessKeyGestures()) {
+ supportedGestures.add(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS);
+ }
+ mInputManager.registerKeyGestureEventHandler(supportedGestures,
+ PhoneWindowManager.this::handleKeyGestureEvent);
}
@VisibleForTesting
- boolean handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) {
+ void handleKeyGestureEvent(KeyGestureEvent event, IBinder focusedToken) {
boolean start = event.getAction() == KeyGestureEvent.ACTION_GESTURE_START;
boolean complete = event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
&& !event.isCancelled();
@@ -4262,12 +4336,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
int modifierState = event.getModifierState();
boolean keyguardOn = keyguardOn();
boolean canLaunchApp = isUserSetupComplete() && !keyguardOn;
+ if (!event.isCancelled() && Arrays.stream(event.getKeycodes()).anyMatch(
+ (keycode) -> keycode == KeyEvent.KEYCODE_POWER)) {
+ mPowerKeyHandled = true;
+ }
switch (gestureType) {
case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS:
if (complete) {
showRecentApps(false);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH:
if (!keyguardOn) {
if (start) {
@@ -4276,7 +4354,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
toggleRecentApps();
}
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
if (complete && canLaunchApp) {
@@ -4284,33 +4362,33 @@ public class PhoneWindowManager implements WindowManagerPolicy {
deviceId, SystemClock.uptimeMillis(),
AssistUtils.INVOCATION_TYPE_UNKNOWN);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_HOME:
if (complete) {
// Post to main thread to avoid blocking input pipeline.
mHandler.post(() -> handleShortPressOnHome(displayId));
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
if (complete && canLaunchApp) {
showSystemSettings();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN:
if (complete) {
lockNow(null /* options */);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
if (complete) {
toggleNotificationPanel();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
if (complete) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
if (complete && mEnableBugReportKeyboardShortcut) {
try {
@@ -4321,12 +4399,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Slog.d(TAG, "Error taking bugreport", e);
}
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_BACK:
if (complete) {
injectBackGesture(SystemClock.uptimeMillis());
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
if (complete) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -4335,7 +4413,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
getTargetDisplayIdForKeyGestureEvent(event));
}
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE:
if (complete) {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
@@ -4344,24 +4422,24 @@ public class PhoneWindowManager implements WindowManagerPolicy {
getTargetDisplayIdForKeyGestureEvent(event));
}
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT:
if (complete) {
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event),
true /* leftOrTop */);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT:
if (complete) {
moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyGestureEvent(event),
false /* leftOrTop */);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
if (complete) {
toggleKeyboardShortcutsMenu(deviceId);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP:
case KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
if (complete) {
@@ -4369,32 +4447,32 @@ public class PhoneWindowManager implements WindowManagerPolicy {
gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP ? 1 : -1;
changeDisplayBrightnessValue(displayId, direction);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER:
if (start) {
showRecentApps(true);
} else {
hideRecentApps(true, false);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS:
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
if (complete && isKeyEventForCurrentUser(event.getDisplayId(),
event.getKeycodes()[0], "launchAllAppsViaA11y")) {
launchAllAppsAction();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH:
if (complete && canLaunchApp) {
launchTargetSearchActivity();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
if (complete) {
int direction = (modifierState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
sendSwitchKeyboardLayout(displayId, focusedToken, direction);
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD:
if (start) {
// Screenshot chord is pressed: Wait for long press delay before taking
@@ -4404,14 +4482,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else {
cancelPendingScreenshotChordAction();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD:
if (start) {
interceptRingerToggleChord();
} else {
cancelPendingRingerToggleChordAction();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS:
if (start) {
performHapticFeedback(
@@ -4421,40 +4499,34 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else {
cancelGlobalActionsAction();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT:
if (start) {
interceptBugreportGestureTv();
} else {
cancelBugreportGestureTv();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT:
if (complete && mAccessibilityShortcutController.isAccessibilityShortcutAvailable(
isKeyguardLocked())) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_ACCESSIBILITY_SHORTCUT));
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS:
if (complete) {
mContext.closeSystemDialogs();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
- if (enableTalkbackAndMagnifierKeyGestures()) {
- if (complete) {
- mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
- TalkbackShortcutController.ShortcutSource.KEYBOARD);
- }
- return true;
+ if (complete) {
+ mTalkbackShortcutController.toggleTalkback(mCurrentUserId,
+ TalkbackShortcutController.ShortcutSource.KEYBOARD);
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
- if (enableVoiceAccessKeyGestures()) {
- if (complete) {
- mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId);
- }
- return true;
+ if (complete) {
+ mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId);
}
break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
@@ -4463,7 +4535,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
&& mModifierShortcutManager.launchApplication(data)) {
dismissKeyboardShortcutsMenu();
}
- return true;
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB:
NotificationManager nm = getNotificationService();
if (nm != null) {
@@ -4472,9 +4544,12 @@ public class PhoneWindowManager implements WindowManagerPolicy {
: Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null,
"Key gesture DND", true);
}
- return true;
+ break;
+ default:
+ Log.w(TAG, "Received a key gesture " + event
+ + " that was not registered by this handler");
+ break;
}
- return false;
}
private void changeDisplayBrightnessValue(int displayId, int direction) {
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 5ee9b7d09fdd..46c497d04f9e 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -176,7 +176,9 @@ public class ThermalManagerService extends SystemService {
try {
final HeadroomCallbackData data;
synchronized (mTemperatureWatcher.mSamples) {
- Slog.d(TAG, "Updating skin threshold: " + threshold);
+ if (DEBUG) {
+ Slog.d(TAG, "Updating skin threshold: " + threshold);
+ }
mTemperatureWatcher.updateTemperatureThresholdLocked(threshold, true);
data = mTemperatureWatcher.getHeadroomCallbackDataLocked();
}
@@ -454,7 +456,9 @@ public class ThermalManagerService extends SystemService {
&& temperature.getType() == Temperature.TYPE_SKIN) {
final HeadroomCallbackData data;
synchronized (mTemperatureWatcher.mSamples) {
- Slog.d(TAG, "Updating new temperature: " + temperature);
+ if (DEBUG) {
+ Slog.d(TAG, "Updating new temperature: " + temperature);
+ }
mTemperatureWatcher.updateTemperatureSampleLocked(System.currentTimeMillis(),
temperature);
mTemperatureWatcher.mCachedHeadrooms.clear();
@@ -1878,6 +1882,7 @@ public class ThermalManagerService extends SystemService {
@VisibleForTesting
long mInactivityThresholdMillis = INACTIVITY_THRESHOLD_MILLIS;
+ @GuardedBy("mSamples")
private final Handler mHandler = BackgroundThread.getHandler();
/**
@@ -1900,6 +1905,9 @@ public class ThermalManagerService extends SystemService {
@GuardedBy("mSamples")
private long mLastForecastCallTimeMillis = 0;
+ private final Runnable mGetAndUpdateTemperatureSamplesRunnable =
+ this::getAndUpdateTemperatureSamples;
+
void getAndUpdateThresholds() {
List<TemperatureThreshold> thresholds =
mHalWrapper.getTemperatureThresholds(true, Temperature.TYPE_SKIN);
@@ -1930,7 +1938,9 @@ public class ThermalManagerService extends SystemService {
return;
}
if (override) {
- Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold);
+ if (DEBUG) {
+ Slog.d(TAG, "Headroom cache cleared on threshold update " + threshold);
+ }
mCachedHeadrooms.clear();
Arrays.fill(mHeadroomThresholds, Float.NaN);
}
@@ -1962,7 +1972,7 @@ public class ThermalManagerService extends SystemService {
< mInactivityThresholdMillis) {
// Trigger this again after a second as long as forecast has been called more
// recently than the inactivity timeout
- mHandler.postDelayed(this::getAndUpdateTemperatureSamples, 1000);
+ mHandler.postDelayed(mGetAndUpdateTemperatureSamplesRunnable, 1000);
} else {
// Otherwise, we've been idle for at least 10 seconds, so we should
// shut down
@@ -1974,6 +1984,9 @@ public class ThermalManagerService extends SystemService {
long now = SystemClock.elapsedRealtime();
final List<Temperature> temperatures = mHalWrapper.getCurrentTemperatures(true,
Temperature.TYPE_SKIN);
+ if (DEBUG) {
+ Slog.d(TAG, "Thermal HAL getCurrentTemperatures result: " + temperatures);
+ }
for (Temperature temperature : temperatures) {
updateTemperatureSampleLocked(now, temperature);
}
@@ -2080,10 +2093,16 @@ public class ThermalManagerService extends SystemService {
}
synchronized (mSamples) {
mLastForecastCallTimeMillis = SystemClock.elapsedRealtime();
- if (mSamples.isEmpty()) {
+ if (!mHandler.hasCallbacks(mGetAndUpdateTemperatureSamplesRunnable)) {
+ if (DEBUG) {
+ Slog.d(TAG, "No temperature update callback, scheduling one");
+ }
getAndUpdateTemperatureSamples();
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, "Temperature update callback already exists");
+ }
}
-
// If somehow things take much longer than expected or there are no temperatures
// to sample, return early
if (mSamples.isEmpty()) {
@@ -2103,8 +2122,11 @@ public class ThermalManagerService extends SystemService {
Binder.getCallingUid(),
FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS,
headroom, forecastSeconds);
- Slog.d(TAG, "Headroom forecast in " + forecastSeconds + "s served from cache: "
- + headroom);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Headroom forecast in " + forecastSeconds + "s served from cache: "
+ + headroom);
+ }
return headroom;
}
@@ -2133,7 +2155,10 @@ public class ThermalManagerService extends SystemService {
Binder.getCallingUid(),
FrameworkStatsLog.THERMAL_HEADROOM_CALLED__API_STATUS__SUCCESS,
headroom, 0);
- Slog.d(TAG, "Headroom forecast in 0s served from cache: " + headroom);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Headroom forecast in 0s served from cache: " + headroom);
+ }
return headroom;
}
// Don't try to forecast, just use the latest one we have
@@ -2182,7 +2207,9 @@ public class ThermalManagerService extends SystemService {
getForecast(DEFAULT_FORECAST_SECONDS),
DEFAULT_FORECAST_SECONDS,
Arrays.copyOf(mHeadroomThresholds, mHeadroomThresholds.length));
- Slog.d(TAG, "New headroom callback data: " + data);
+ if (DEBUG) {
+ Slog.d(TAG, "New headroom callback data: " + data);
+ }
return data;
}
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
index 8c3b7c606f04..3ece07c84080 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionService.java
@@ -17,6 +17,7 @@
package com.android.server.security.advancedprotection;
import static android.provider.Settings.Secure.ADVANCED_PROTECTION_MODE;
+import static android.provider.Settings.Secure.AAPM_USB_DATA_PROTECTION;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.Manifest;
@@ -69,6 +70,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
/** @hide */
public class AdvancedProtectionService extends IAdvancedProtectionService.Stub {
@@ -129,7 +131,10 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
Slog.e(TAG, "Failed to initialize DisallowCellular2g", e);
}
}
- if (android.security.Flags.aapmFeatureUsbDataProtection()) {
+ if (android.security.Flags.aapmFeatureUsbDataProtection()
+ // Usb data protection is enabled by default
+ && mStore.retrieveInt(AAPM_USB_DATA_PROTECTION, AdvancedProtectionStore.ON)
+ == AdvancedProtectionStore.ON) {
try {
mHooks.add(new UsbDataAdvancedProtectionHook(mContext, enabled));
} catch (Exception e) {
@@ -183,7 +188,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
// Without permission check
private boolean isAdvancedProtectionEnabledInternal() {
- return mStore.retrieve();
+ return mStore.retrieveAdvancedProtectionModeEnabled();
}
@Override
@@ -217,7 +222,7 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
try {
synchronized (mCallbacks) {
if (enabled != isAdvancedProtectionEnabledInternal()) {
- mStore.store(enabled);
+ mStore.storeAdvancedProtectionModeEnabled(enabled);
sendModeChanged(enabled);
logAdvancedProtectionEnabled(enabled);
}
@@ -227,6 +232,34 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
}
}
+ public void setUsbDataProtectionEnabled(boolean enabled) {
+ int value = enabled ? AdvancedProtectionStore.ON
+ : AdvancedProtectionStore.OFF;
+ setAdvancedProtectionSubSettingInt(AAPM_USB_DATA_PROTECTION, value);
+ }
+
+ private void setAdvancedProtectionSubSettingInt(String key, int value) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mCallbacks) {
+ mStore.storeInt(key, value);
+ Slog.i(TAG, "Advanced protection: subsetting" + key + " is " + value);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ public boolean isUsbDataProtectionEnabled() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return mStore.retrieveInt(AAPM_USB_DATA_PROTECTION, AdvancedProtectionStore.ON)
+ == AdvancedProtectionStore.ON;
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
@EnforcePermission(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE)
public void logDialogShown(@FeatureId int featureId, @SupportDialogType int type,
@@ -419,8 +452,8 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
@VisibleForTesting
static class AdvancedProtectionStore {
private final Context mContext;
- private static final int APM_ON = 1;
- private static final int APM_OFF = 0;
+ static final int ON = 1;
+ static final int OFF = 0;
private final UserManagerInternal mUserManager;
AdvancedProtectionStore(@NonNull Context context) {
@@ -428,15 +461,26 @@ public class AdvancedProtectionService extends IAdvancedProtectionService.Stub
mUserManager = LocalServices.getService(UserManagerInternal.class);
}
- void store(boolean enabled) {
+ void storeAdvancedProtectionModeEnabled(boolean enabled) {
+ Settings.Secure.putIntForUser(mContext.getContentResolver(),
+ ADVANCED_PROTECTION_MODE, enabled ? ON : OFF,
+ mUserManager.getMainUserId());
+ }
+
+ boolean retrieveAdvancedProtectionModeEnabled() {
+ return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ ADVANCED_PROTECTION_MODE, OFF, mUserManager.getMainUserId()) == ON;
+ }
+
+ void storeInt(String key, int value) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
- ADVANCED_PROTECTION_MODE, enabled ? APM_ON : APM_OFF,
+ key, value,
mUserManager.getMainUserId());
}
- boolean retrieve() {
+ int retrieveInt(String key, int defaultValue) {
return Settings.Secure.getIntForUser(mContext.getContentResolver(),
- ADVANCED_PROTECTION_MODE, APM_OFF, mUserManager.getMainUserId()) == APM_ON;
+ key, defaultValue, mUserManager.getMainUserId());
}
}
diff --git a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java
index 42505ad2de3f..ae17a459010b 100644
--- a/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java
+++ b/services/core/java/com/android/server/security/advancedprotection/AdvancedProtectionShellCommand.java
@@ -45,6 +45,10 @@ class AdvancedProtectionShellCommand extends ShellCommand {
return setProtectionEnabled();
case "is-protection-enabled":
return isProtectionEnabled(pw);
+ case "set-usb-data-protection-enabled":
+ return setUsbDataProtectedEnabled();
+ case "is-usb-data-protection-enabled":
+ return isUsbDataProtectedEnabled(pw);
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
@@ -64,6 +68,10 @@ class AdvancedProtectionShellCommand extends ShellCommand {
pw.println(" Print this help text.");
pw.println(" set-protection-enabled [true|false]");
pw.println(" is-protection-enabled");
+ if(android.security.Flags.aapmFeatureUsbDataProtection()) {
+ pw.println(" set-usb-data-protection-enabled [true|false]");
+ pw.println(" is-usb-data-protection-enabled");
+ }
}
@SuppressLint("AndroidFrameworkRequiresPermission")
@@ -79,4 +87,22 @@ class AdvancedProtectionShellCommand extends ShellCommand {
pw.println(protectionMode);
return 0;
}
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int setUsbDataProtectedEnabled() throws RemoteException {
+ if(android.security.Flags.aapmFeatureUsbDataProtection()) {
+ String protectionMode = getNextArgRequired();
+ mService.setUsbDataProtectionEnabled(Boolean.parseBoolean(protectionMode));
+ }
+ return 0;
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private int isUsbDataProtectedEnabled(@NonNull PrintWriter pw) throws RemoteException {
+ if(android.security.Flags.aapmFeatureUsbDataProtection()) {
+ boolean protectionMode = mService.isUsbDataProtectionEnabled();
+ pw.println(protectionMode);
+ }
+ return 0;
+ }
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 798c794edaf5..0f6cc24f1fc9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -87,6 +87,7 @@ import android.service.quicksettings.TileService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.IndentingPrintWriter;
+import android.util.IntArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -102,6 +103,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.logging.InstanceId;
import com.android.internal.os.TransferPipe;
+import com.android.internal.statusbar.DisableStates;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBar;
@@ -124,6 +126,7 @@ import com.android.server.policy.GlobalActionsProvider;
import com.android.server.power.ShutdownCheckPoints;
import com.android.server.power.ShutdownThread;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -1344,48 +1347,76 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
return mTracingEnabled;
}
- // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable status bar features. Pass the bitwise-or of the {@code #DISABLE_*} flags.
+ * To re-enable everything, pass {@code #DISABLE_NONE}.
+ *
+ * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+ * {@code #DISABLE2_*} flags.
+ */
@Override
public void disable(int what, IBinder token, String pkg) {
disableForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
+ /**
+ * Disable status bar features for a given user. Pass the bitwise-or of the
+ * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}.
+ *
+ * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use
+ * {@code #DISABLE2_*} flags.
+ */
@Override
public void disableForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
enforceValidCallingUser();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1);
+ if (Flags.statusBarConnectedDisplays()) {
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableAllDisplaysLocked(displayIds, userId, what, token, pkg, /* whichFlag= */ 1);
+ } else {
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, /* whichFlag= */ 1);
+ }
}
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags.
- * To re-enable everything, pass {@link #DISABLE2_NONE}.
+ * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags.
+ * To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2(int what, IBinder token, String pkg) {
disable2ForUser(what, token, pkg, mCurrentUserId);
}
- // TODO(b/117478341): make it aware of multi-display if needed.
/**
- * Disable additional status bar features for a given user. Pass the bitwise-or of the
- * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}.
+ * Disable additional status bar features for a given user. Pass the bitwise-or
+ * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}.
*
- * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags.
+ * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use
+ * {@code #DISABLE_*} flags.
*/
@Override
public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
enforceStatusBar();
synchronized (mLock) {
- disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2);
+ if (Flags.statusBarConnectedDisplays()) {
+ IntArray displayIds = new IntArray();
+ for (int i = 0; i < mDisplayUiState.size(); i++) {
+ displayIds.add(mDisplayUiState.keyAt(i));
+ }
+ disableAllDisplaysLocked(displayIds, userId, what, token, pkg, /* whichFlag= */ 2);
+ } else {
+ disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, /* whichFlag= */ 2);
+ }
}
}
@@ -1414,6 +1445,42 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
}
}
+ // This method batches disable state across all displays into a single remote call
+ // (IStatusBar#disableForAllDisplays) for efficiency and calls
+ // NotificationDelegate#onSetDisabled only if any display's disable state changes.
+ private void disableAllDisplaysLocked(IntArray displayIds, int userId, int what, IBinder token,
+ String pkg, int whichFlag) {
+ // It's important that the the callback and the call to mBar get done
+ // in the same order when multiple threads are calling this function
+ // so they are paired correctly. The messages on the handler will be
+ // handled in the order they were enqueued, but will be outside the lock.
+ manageDisableListLocked(userId, what, token, pkg, whichFlag);
+
+ // Ensure state for the current user is applied, even if passed a non-current user.
+ final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1);
+ final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2);
+
+ IStatusBar bar = mBar;
+ Map<Integer, Pair<Integer, Integer>> displaysWithNewDisableStates = new HashMap<>();
+ for (int displayId : displayIds.toArray()) {
+ final UiState state = getUiState(displayId);
+ if (!state.disableEquals(net1, net2)) {
+ state.setDisabled(net1, net2);
+ displaysWithNewDisableStates.put(displayId, new Pair(net1, net2));
+ }
+ }
+ if (bar != null) {
+ try {
+ bar.disableForAllDisplays(new DisableStates(displaysWithNewDisableStates));
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Unable to disable Status bar.", ex);
+ }
+ }
+ if (!displaysWithNewDisableStates.isEmpty()) {
+ mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1));
+ }
+ }
+
/**
* Get the currently applied disable flags, in the form of one Pair<Integer, Integer>.
*
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
index cb82f480d73b..d152a1dbe17d 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraOverrides.java
@@ -35,6 +35,7 @@ import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.window.DesktopModeFlags;
import com.android.server.wm.utils.OptPropFactory;
import com.android.window.flags.Flags;
@@ -177,7 +178,7 @@ class AppCompatCameraOverrides {
* </ul>
*/
boolean shouldApplyFreeformTreatmentForCameraCompat() {
- return Flags.enableCameraCompatForDesktopWindowing()
+ return DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()
&& (shouldEnableCameraCompatFreeformTreatmentForApp()
|| shouldEnableCameraCompatFreeformTreatmentForAllApps());
}
diff --git a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
index 276c7d2cbaa0..e7a0803df916 100644
--- a/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatCameraPolicy.java
@@ -26,9 +26,9 @@ import android.app.CameraCompatTaskInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.res.Configuration;
import android.widget.Toast;
+import android.window.DesktopModeFlags;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.window.flags.Flags;
/**
* Encapsulate policy logic related to app compat display rotation.
@@ -53,7 +53,7 @@ class AppCompatCameraPolicy {
final boolean needsDisplayRotationCompatPolicy =
wmService.mAppCompatConfiguration.isCameraCompatTreatmentEnabledAtBuildTime();
final boolean needsCameraCompatFreeformPolicy =
- Flags.enableCameraCompatForDesktopWindowing()
+ DesktopModeFlags.ENABLE_CAMERA_COMPAT_SIMULATE_REQUESTED_ORIENTATION.isTrue()
&& DesktopModeHelper.canEnterDesktopMode(wmService.mContext);
if (needsDisplayRotationCompatPolicy || needsCameraCompatFreeformPolicy) {
mCameraStateMonitor = new CameraStateMonitor(displayContent, wmService.mH);
diff --git a/services/core/java/com/android/server/wm/DeviceStateAutoRotateSettingIssueLogger.java b/services/core/java/com/android/server/wm/DeviceStateAutoRotateSettingIssueLogger.java
index 937039d838e5..a7d03485058f 100644
--- a/services/core/java/com/android/server/wm/DeviceStateAutoRotateSettingIssueLogger.java
+++ b/services/core/java/com/android/server/wm/DeviceStateAutoRotateSettingIssueLogger.java
@@ -29,13 +29,13 @@ import java.util.function.LongSupplier;
/**
* Logs potential race conditions that lead to incorrect auto-rotate setting.
*
- * Before go/auto-rotate-refactor, there is a race condition that happen during device state
+ * <p>Before go/auto-rotate-refactor, there is a race condition that happen during device state
* changes, as a result, incorrect auto-rotate setting are written for a device state in
* DEVICE_STATE_ROTATION_LOCK. Realistically, users shouldn’t be able to change
* DEVICE_STATE_ROTATION_LOCK while the device folds/unfolds.
*
- * This class monitors the time between a device state change and a subsequent change to the device
- * state based auto-rotate setting. If the duration is less than a threshold
+ * <p>This class monitors the time between a device state change and a subsequent change to the
+ * device state based auto-rotate setting. If the duration is less than a threshold
* (DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_THRESHOLD), a potential issue is logged. The logging of
* the atom is not expected to occur often, realistically estimated once a month on few devices.
* But the number could be bigger, as that's what this metric is set to reveal.
@@ -72,23 +72,33 @@ public class DeviceStateAutoRotateSettingIssueLogger {
}
private void onStateChange() {
- // Only move forward if both of the events have occurred already
- if (mLastDeviceStateChangeTime != TIME_NOT_SET
- && mLastDeviceStateAutoRotateSettingChangeTime != TIME_NOT_SET) {
- final long duration =
- mLastDeviceStateAutoRotateSettingChangeTime - mLastDeviceStateChangeTime;
- boolean isDeviceStateChangeFirst = duration > 0;
+ // Only move forward if both of the events have occurred already.
+ if (mLastDeviceStateChangeTime == TIME_NOT_SET
+ || mLastDeviceStateAutoRotateSettingChangeTime == TIME_NOT_SET) {
+ return;
+ }
+ final long duration =
+ mLastDeviceStateAutoRotateSettingChangeTime - mLastDeviceStateChangeTime;
+ boolean isDeviceStateChangeFirst = duration > 0;
- if (abs(duration)
- < DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_THRESHOLD_MILLIS) {
- FrameworkStatsLog.write(
- FrameworkStatsLog.DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_REPORTED,
- (int) abs(duration),
- isDeviceStateChangeFirst);
- }
+ if (abs(duration)
+ < DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_THRESHOLD_MILLIS) {
+ FrameworkStatsLog.write(
+ FrameworkStatsLog.DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_REPORTED,
+ (int) abs(duration),
+ isDeviceStateChangeFirst);
+ // This pair is logged, reset both timestamps.
mLastDeviceStateAutoRotateSettingChangeTime = TIME_NOT_SET;
mLastDeviceStateChangeTime = TIME_NOT_SET;
+ } else {
+ // This pair was not logged, reset the earlier timestamp.
+ if (isDeviceStateChangeFirst) {
+ mLastDeviceStateChangeTime = TIME_NOT_SET;
+ } else {
+ mLastDeviceStateAutoRotateSettingChangeTime = TIME_NOT_SET;
+ }
}
+
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 5bb64bc05f05..ec17d131958b 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -35,8 +35,6 @@ import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
-import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
-import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
@@ -51,7 +49,6 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.SurfaceControl.METADATA_TASK_ID;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -132,7 +129,6 @@ import android.app.IActivityController;
import android.app.PictureInPictureParams;
import android.app.TaskInfo;
import android.app.WindowConfiguration;
-import android.app.compat.CompatChanges;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -514,10 +510,16 @@ class Task extends TaskFragment {
boolean mIsPerceptible = false;
/**
- * Whether the compatibility overrides that change the resizability of the app should be allowed
- * for the specific app.
+ * Whether the task has been forced resizable, which is determined by the
+ * activity that started this task.
*/
- boolean mAllowForceResizeOverride = true;
+ private boolean mForceResizeOverride;
+
+ /**
+ * Whether the task has been forced non-resizable, which is determined by
+ * the activity that started this task.
+ */
+ private boolean mForceNonResizeOverride;
private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_TASK_MSG + 1;
@@ -675,7 +677,6 @@ class Task extends TaskFragment {
intent = _intent;
mMinWidth = minWidth;
mMinHeight = minHeight;
- updateAllowForceResizeOverride();
}
mAtmService.getTaskChangeNotificationController().notifyTaskCreated(_taskId, realActivity);
mHandler = new ActivityTaskHandler(mTaskSupervisor.mLooper);
@@ -946,6 +947,7 @@ class Task extends TaskFragment {
mCallingPackage = r.launchedFromPackage;
mCallingFeatureId = r.launchedFromFeatureId;
setIntent(intent != null ? intent : r.intent, info != null ? info : r.info);
+ updateForceResizeOverrides(r);
}
setLockTaskAuth(r);
}
@@ -1038,7 +1040,6 @@ class Task extends TaskFragment {
mTaskSupervisor.mRecentTasks.remove(this);
mTaskSupervisor.mRecentTasks.add(this);
}
- updateAllowForceResizeOverride();
}
/** Sets the original minimal width and height. */
@@ -1855,15 +1856,14 @@ class Task extends TaskFragment {
-1 /* don't check PID */, -1 /* don't check UID */, this);
}
- private void updateAllowForceResizeOverride() {
- try {
- mAllowForceResizeOverride = mAtmService.mContext.getPackageManager().getPropertyAsUser(
- PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES,
- getBasePackageName(), null /* className */, mUserId).getBoolean();
- } catch (PackageManager.NameNotFoundException e) {
- // Package not found or property not defined, reset to default value.
- mAllowForceResizeOverride = true;
- }
+ private void updateForceResizeOverrides(@NonNull ActivityRecord r) {
+ final AppCompatResizeOverrides resizeOverrides = r.mAppCompatController
+ .getResizeOverrides();
+ mForceResizeOverride = resizeOverrides.shouldOverrideForceResizeApp()
+ || r.isUniversalResizeable()
+ || r.mAppCompatController.getAspectRatioOverrides()
+ .hasFullscreenOverride();
+ mForceNonResizeOverride = resizeOverrides.shouldOverrideForceNonResizeApp();
}
/**
@@ -2882,17 +2882,8 @@ class Task extends TaskFragment {
final boolean forceResizable = mAtmService.mForceResizableActivities
&& getActivityType() == ACTIVITY_TYPE_STANDARD;
if (forceResizable) return true;
-
- final UserHandle userHandle = UserHandle.getUserHandleForUid(mUserId);
- final boolean forceResizableOverride = mAllowForceResizeOverride
- && CompatChanges.isChangeEnabled(
- FORCE_RESIZE_APP, getBasePackageName(), userHandle);
- final boolean forceNonResizableOverride = mAllowForceResizeOverride
- && CompatChanges.isChangeEnabled(
- FORCE_NON_RESIZE_APP, getBasePackageName(), userHandle);
-
- if (forceNonResizableOverride) return false;
- return forceResizableOverride || ActivityInfo.isResizeableMode(mResizeMode)
+ if (mForceNonResizeOverride) return false;
+ return mForceResizeOverride || ActivityInfo.isResizeableMode(mResizeMode)
|| (mSupportsPictureInPicture && checkPictureInPictureSupport);
}
diff --git a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
index 8c798759c890..665c5cffd9ff 100644
--- a/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
+++ b/services/core/java/com/android/server/wm/TaskFpsCallbackController.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.window.ITaskFpsCallback;
@@ -25,12 +24,10 @@ import java.util.HashMap;
final class TaskFpsCallbackController {
- private final Context mContext;
private final HashMap<IBinder, Long> mTaskFpsCallbacks;
private final HashMap<IBinder, IBinder.DeathRecipient> mDeathRecipients;
- TaskFpsCallbackController(Context context) {
- mContext = context;
+ TaskFpsCallbackController() {
mTaskFpsCallbacks = new HashMap<>();
mDeathRecipients = new HashMap<>();
}
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 51c3da098020..02b53b0106b8 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -512,9 +512,14 @@ class TransitionController {
return false;
}
+ /** Returns {@code true} if the display contains a collecting transition. */
+ boolean isCollectingTransitionOnDisplay(@NonNull DisplayContent dc) {
+ return mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc);
+ }
+
/** Returns {@code true} if the display contains a running or pending transition. */
boolean isTransitionOnDisplay(@NonNull DisplayContent dc) {
- if (mCollectingTransition != null && mCollectingTransition.isOnDisplay(dc)) {
+ if (isCollectingTransitionOnDisplay(dc)) {
return true;
}
for (int i = mWaitingTransitions.size() - 1; i >= 0; --i) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index ebf8d35fd9be..3ccbc868377e 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1449,7 +1449,7 @@ public class WindowManagerService extends IWindowManager.Stub
mPresentationController = new PresentationController();
mBlurController = new BlurController(mContext, mPowerManager);
- mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
+ mTaskFpsCallbackController = new TaskFpsCallbackController();
mAccessibilityController = new AccessibilityController(this);
mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
@@ -1845,9 +1845,12 @@ public class WindowManagerService extends IWindowManager.Stub
// Only a presentation window needs a transition because its visibility affets the
// lifecycle of apps below (b/390481865).
if (enablePresentationForConnectedDisplays() && win.isPresentation()) {
- Transition transition = null;
+ final boolean wasTransitionOnDisplay =
+ win.mTransitionController.isCollectingTransitionOnDisplay(displayContent);
+ Transition newlyCreatedTransition = null;
if (!win.mTransitionController.isCollecting()) {
- transition = win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
+ newlyCreatedTransition =
+ win.mTransitionController.createAndStartCollecting(TRANSIT_OPEN);
}
win.mTransitionController.collect(win.mToken);
res |= addWindowInner(win, displayPolicy, activity, displayContent, outInsetsState,
@@ -1856,9 +1859,14 @@ public class WindowManagerService extends IWindowManager.Stub
// A presentation hides all activities behind on the same display.
win.mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
/*notifyClients=*/ true);
- win.mTransitionController.getCollectingTransition().setReady(win.mToken, true);
- if (transition != null) {
- win.mTransitionController.requestStartTransition(transition, null,
+ if (!wasTransitionOnDisplay && win.mTransitionController
+ .isCollectingTransitionOnDisplay(displayContent)) {
+ // Set the display ready only when the display gets added to the collecting
+ // transition in this operation.
+ win.mTransitionController.setReady(win.mToken);
+ }
+ if (newlyCreatedTransition != null) {
+ win.mTransitionController.requestStartTransition(newlyCreatedTransition, null,
null /* remoteTransition */, null /* displayChange */);
}
} else {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 22ddd5f39b24..d43aba0d218d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2365,9 +2365,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Only a presentation window needs a transition because its visibility affets the
// lifecycle of apps below (b/390481865).
if (enablePresentationForConnectedDisplays() && isPresentation()) {
- Transition transition = null;
+ final boolean wasTransitionOnDisplay =
+ mTransitionController.isCollectingTransitionOnDisplay(displayContent);
+ Transition newlyCreatedTransition = null;
if (!mTransitionController.isCollecting()) {
- transition = mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
+ newlyCreatedTransition =
+ mTransitionController.createAndStartCollecting(TRANSIT_CLOSE);
}
mTransitionController.collect(mToken);
mAnimatingExit = true;
@@ -2376,9 +2379,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// A presentation hides all activities behind on the same display.
mDisplayContent.ensureActivitiesVisible(/*starting=*/ null,
/*notifyClients=*/ true);
- mTransitionController.getCollectingTransition().setReady(mToken, true);
- if (transition != null) {
- mTransitionController.requestStartTransition(transition, null,
+ if (!wasTransitionOnDisplay && mTransitionController
+ .isCollectingTransitionOnDisplay(displayContent)) {
+ // Set the display ready only when the display gets added to the collecting
+ // transition in this operation.
+ mTransitionController.setReady(mToken);
+ }
+ if (newlyCreatedTransition != null) {
+ mTransitionController.requestStartTransition(newlyCreatedTransition, null,
null /* remoteTransition */, null /* displayChange */);
}
} else {
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index ec8794f8073f..017284cded8e 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1978,6 +1978,11 @@ NativeInputManager::interceptKeyBeforeDispatching(const sp<IBinder>& token,
return inputdispatcher::KeyEntry::InterceptKeyResult::SKIP;
}
+ // -2 : Skip sending even to application and go directly to post processing e.g. fallbacks.
+ if (delayMillis == -2) {
+ return inputdispatcher::KeyEntry::InterceptKeyResult::FALLBACK;
+ }
+
return milliseconds_to_nanoseconds(delayMillis);
}
diff --git a/services/credentials/java/com/android/server/credentials/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index ac4aac694c3a..11edb93dffea 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -383,7 +383,9 @@ public class MetricUtilities {
/* api_name */
initialPhaseMetric.getApiName(),
/* primary_candidates_indicated */
- candidatePrimaryProviderList
+ candidatePrimaryProviderList,
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during candidate provider uid metric emit: " + e);
@@ -442,7 +444,9 @@ public class MetricUtilities {
/* autofill_session_id */
initialPhaseMetric.getAutofillSessionId(),
/* autofill_request_id */
- initialPhaseMetric.getAutofillRequestId()
+ initialPhaseMetric.getAutofillRequestId(),
+ /* api_prepared */
+ initialPhaseMetric.hasApiUsedPrepareFlow()
);
} catch (Exception e) {
Slog.w(TAG, "Unexpected error during initial metric emit: " + e);
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index d60807c7b001..2d4360edf3a8 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -27,6 +27,7 @@ import android.credentials.GetCredentialRequest;
import android.credentials.IGetCredentialCallback;
import android.credentials.IPrepareGetCredentialCallback;
import android.credentials.PrepareGetCredentialResponseInternal;
+import android.credentials.flags.Flags;
import android.credentials.selection.GetCredentialProviderData;
import android.credentials.selection.ProviderData;
import android.credentials.selection.RequestInfo;
@@ -60,7 +61,12 @@ public class PrepareGetRequestSession extends GetRequestSession {
int numTypes = (request.getCredentialOptions().stream()
.map(CredentialOption::getType).collect(
Collectors.toSet())).size(); // Dedupe type strings
- mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ if (!Flags.fixMetricDuplicationEmits()) {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request);
+ } else {
+ mRequestSessionMetric.collectGetFlowInitialMetricInfo(request,
+ /*isApiPrepared=*/ true);
+ }
mPrepareGetCredentialCallback = prepareGetCredentialCallback;
Slog.i(TAG, "PrepareGetRequestSession constructed.");
diff --git a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
index 8a4e86c440b3..811b97a5bf03 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/InitialPhaseMetric.java
@@ -55,6 +55,9 @@ public class InitialPhaseMetric {
// The request id of autofill if the request is from autofill, defaults to -1
private int mAutofillRequestId = -1;
+ // Indicates if this API call used the prepare flow, defaults to false
+ private boolean mApiUsedPrepareFlow = false;
+
public InitialPhaseMetric(int sessionIdTrackOne) {
mSessionIdCaller = sessionIdTrackOne;
@@ -173,4 +176,17 @@ public class InitialPhaseMetric {
public int[] getUniqueRequestCounts() {
return mRequestCounts.values().stream().mapToInt(Integer::intValue).toArray();
}
+
+ /* ------ API Prepared ------ */
+
+ public void setApiUsedPrepareFlow(boolean apiUsedPrepareFlow) {
+ mApiUsedPrepareFlow = apiUsedPrepareFlow;
+ }
+
+ /**
+ * @return a boolean indicating if this API call utilized a prepare flow
+ */
+ public boolean hasApiUsedPrepareFlow() {
+ return mApiUsedPrepareFlow;
+ }
}
diff --git a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
index 619a56846e95..dc1747f803ea 100644
--- a/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
+++ b/services/credentials/java/com/android/server/credentials/metrics/RequestSessionMetric.java
@@ -225,6 +225,22 @@ public class RequestSessionMetric {
}
/**
+ * Collects initializations for Get flow metrics.
+ *
+ * @param request the get credential request containing information to parse for metrics
+ * @param isApiPrepared indicates this API flow utilized the 'prepare' flow
+ */
+ public void collectGetFlowInitialMetricInfo(GetCredentialRequest request,
+ boolean isApiPrepared) {
+ try {
+ collectGetFlowInitialMetricInfo(request);
+ mInitialPhaseMetric.setApiUsedPrepareFlow(isApiPrepared);
+ } catch (Exception e) {
+ Slog.i(TAG, "Unexpected error collecting get flow initial metric: " + e);
+ }
+ }
+
+ /**
* During browsing, where multiple entries can be selected, this collects the browsing phase
* metric information. This is emitted together with the final phase, and the recursive path
* with authentication entries, which may occur in rare circumstances, are captured.
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 232bb83fdf9f..5a140d53a4d8 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1753,6 +1753,13 @@ class AppIdPermissionPolicy : SchemePolicy() {
}
val appIdPermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdPermissionFlags()
val permissionFlags = appIdPermissionFlags.mutateOrPut(appId) { MutableIndexedMap() }
+ // for debugging possible races TODO(b/401768134)
+ oldState.userStates[userId]?.appIdPermissionFlags[appId]?.map?.let {
+ if (permissionFlags.map === it) {
+ throw IllegalStateException("Unexpected sharing between old/new state")
+ }
+ }
+
permissionFlags.putWithDefault(permissionName, newFlags, 0)
if (permissionFlags.isEmpty()) {
appIdPermissionFlags -= appId
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index bb6339c79502..0b5a95b0e888 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -27,6 +27,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.supervision.ISupervisionManager;
@@ -147,9 +148,21 @@ public class SupervisionService extends ISupervisionManager.Stub {
@Override
@Nullable
public Intent createConfirmSupervisionCredentialsIntent() {
- // TODO(b/392961554): (1) Return null if supervision is not enabled.
- // (2) check if PIN exists before return a valid intent.
enforceAnyPermission(QUERY_USERS, MANAGE_USERS);
+ if (!isSupervisionEnabledForUser(mContext.getUserId())) {
+ return null;
+ }
+ // Verify the supervising user profile exists and has a secure credential set.
+ final int supervisingUserId = mInjector.getUserManagerInternal().getSupervisingProfileId();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ if (supervisingUserId == UserHandle.USER_NULL
+ || !mInjector.getKeyguardManager().isDeviceSecure(supervisingUserId)) {
+ return null;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
final Intent intent = new Intent(ACTION_CONFIRM_SUPERVISION_CREDENTIALS);
// explicitly set the package for security
intent.setPackage("com.android.settings");
@@ -277,6 +290,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
static class Injector {
private final Context mContext;
private DevicePolicyManagerInternal mDpmInternal;
+ private KeyguardManager mKeyguardManager;
private PackageManager mPackageManager;
private UserManagerInternal mUserManagerInternal;
@@ -292,6 +306,13 @@ public class SupervisionService extends ISupervisionManager.Stub {
return mDpmInternal;
}
+ KeyguardManager getKeyguardManager() {
+ if (mKeyguardManager == null) {
+ mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
+ }
+ return mKeyguardManager;
+ }
+
PackageManager getPackageManager() {
if (mPackageManager == null) {
mPackageManager = mContext.getPackageManager();
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
index 554b5b4297f2..740424813c2a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/UserDataPreparerTest.java
@@ -98,9 +98,9 @@ public class UserDataPreparerTest {
File systemDeDir = mUserDataPreparer.getDataSystemDeDirectory(TEST_USER_ID);
systemDeDir.mkdirs();
mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_DE);
- verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ verify(mStorageManagerMock).prepareUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_DE));
- verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+ verify(mInstaller).createUserData(isNull(), eq(TEST_USER_ID),
eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_DE));
int serialNumber = UserDataPreparer.getSerialNumber(userDeDir);
assertEquals(TEST_USER_SERIAL, serialNumber);
@@ -115,9 +115,9 @@ public class UserDataPreparerTest {
File systemCeDir = mUserDataPreparer.getDataSystemCeDirectory(TEST_USER_ID);
systemCeDir.mkdirs();
mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
- verify(mStorageManagerMock).prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ verify(mStorageManagerMock).prepareUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
- verify(mInstaller).createUserData(isNull(String.class), eq(TEST_USER_ID),
+ verify(mInstaller).createUserData(isNull(), eq(TEST_USER_ID),
eq(TEST_USER_SERIAL), eq(StorageManager.FLAG_STORAGE_CE));
int serialNumber = UserDataPreparer.getSerialNumber(userCeDir);
assertEquals(TEST_USER_SERIAL, serialNumber);
@@ -129,10 +129,10 @@ public class UserDataPreparerTest {
public void testPrepareUserData_forNewUser_destroysOnFailure() throws Exception {
TEST_USER.lastLoggedInTime = 0;
doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
- .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ .prepareUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
- verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ verify(mStorageManagerMock).destroyUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
}
@@ -140,10 +140,10 @@ public class UserDataPreparerTest {
public void testPrepareUserData_forExistingUser_doesNotDestroyOnFailure() throws Exception {
TEST_USER.lastLoggedInTime = System.currentTimeMillis();
doThrow(new IllegalStateException("expected exception for test")).when(mStorageManagerMock)
- .prepareUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ .prepareUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
mUserDataPreparer.prepareUserData(TEST_USER, StorageManager.FLAG_STORAGE_CE);
- verify(mStorageManagerMock, never()).destroyUserStorage(isNull(String.class),
+ verify(mStorageManagerMock, never()).destroyUserStorage(isNull(),
eq(TEST_USER_ID), eq(StorageManager.FLAG_STORAGE_CE));
}
@@ -171,9 +171,9 @@ public class UserDataPreparerTest {
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_DE);
- verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+ verify(mInstaller).destroyUserData(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_DE));
- verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ verify(mStorageManagerMock).destroyUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_DE));
// systemDir (normal path: /data/system/users/$userId) should have been deleted.
@@ -195,9 +195,9 @@ public class UserDataPreparerTest {
mUserDataPreparer.destroyUserData(TEST_USER_ID, StorageManager.FLAG_STORAGE_CE);
- verify(mInstaller).destroyUserData(isNull(String.class), eq(TEST_USER_ID),
+ verify(mInstaller).destroyUserData(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
- verify(mStorageManagerMock).destroyUserStorage(isNull(String.class), eq(TEST_USER_ID),
+ verify(mStorageManagerMock).destroyUserStorage(isNull(), eq(TEST_USER_ID),
eq(StorageManager.FLAG_STORAGE_CE));
// systemCeDir (normal path: /data/system_ce/$userId) should still exist but be empty, since
@@ -225,7 +225,7 @@ public class UserDataPreparerTest {
.reconcileUsers(StorageManager.UUID_PRIVATE_INTERNAL, Arrays.asList(u1, u2),
Arrays.asList(dir1, dir2, dir3));
// Verify that user 3 data is removed
- verify(mInstaller).destroyUserData(isNull(String.class), eq(3),
+ verify(mInstaller).destroyUserData(isNull(), eq(3),
eq(StorageManager.FLAG_STORAGE_DE|StorageManager.FLAG_STORAGE_CE));
}
diff --git a/services/tests/displayservicetests/Android.bp b/services/tests/displayservicetests/Android.bp
index 36ea24195789..c85053d13e68 100644
--- a/services/tests/displayservicetests/Android.bp
+++ b/services/tests/displayservicetests/Android.bp
@@ -51,6 +51,7 @@ android_test {
data: [
":DisplayManagerTestApp",
+ ":TopologyTestApp",
],
certificate: "platform",
diff --git a/services/tests/displayservicetests/AndroidManifest.xml b/services/tests/displayservicetests/AndroidManifest.xml
index 205ff058275a..76f219b7433b 100644
--- a/services/tests/displayservicetests/AndroidManifest.xml
+++ b/services/tests/displayservicetests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.MANAGE_USB" />
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
<!-- Permissions needed for DisplayTransformManagerTest -->
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
diff --git a/services/tests/displayservicetests/AndroidTest.xml b/services/tests/displayservicetests/AndroidTest.xml
index f3697bbffd5c..2fe37233870f 100644
--- a/services/tests/displayservicetests/AndroidTest.xml
+++ b/services/tests/displayservicetests/AndroidTest.xml
@@ -28,6 +28,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="DisplayManagerTestApp.apk" />
+ <option name="test-file-name" value="TopologyTestApp.apk" />
</target_preparer>
<option name="test-tag" value="DisplayServiceTests" />
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
index 1f45792e5097..bf4b61347bab 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayEventDeliveryTest.java
@@ -16,7 +16,6 @@
package com.android.server.display;
-import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.util.DisplayMetrics.DENSITY_HIGH;
@@ -27,19 +26,11 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.content.Context;
import android.content.Intent;
-import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
-import android.os.BinderProxy;
import android.os.Handler;
-import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.os.Messenger;
-import android.platform.test.annotations.AppModeSdkSandbox;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,10 +39,7 @@ import android.util.SparseArray;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
-import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
import com.android.server.am.Flags;
import org.junit.After;
@@ -63,9 +51,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
-import java.io.IOException;
import java.util.Arrays;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -73,8 +59,7 @@ import java.util.concurrent.TimeUnit;
* Tests that applications can receive display events correctly.
*/
@RunWith(Parameterized.class)
-@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
-public class DisplayEventDeliveryTest {
+public class DisplayEventDeliveryTest extends EventDeliveryTestBase {
private static final String TAG = "DisplayEventDeliveryTest";
@Rule
@@ -85,37 +70,17 @@ public class DisplayEventDeliveryTest {
private static final int WIDTH = 720;
private static final int HEIGHT = 480;
- private static final int MESSAGE_LAUNCHED = 1;
- private static final int MESSAGE_CALLBACK = 2;
-
private static final int DISPLAY_ADDED = 1;
private static final int DISPLAY_CHANGED = 2;
private static final int DISPLAY_REMOVED = 3;
- private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100;
- private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
-
private static final String TEST_PACKAGE =
"com.android.servicestests.apps.displaymanagertestapp";
private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity";
private static final String TEST_DISPLAYS = "DISPLAYS";
- private static final String TEST_MESSENGER = "MESSENGER";
private final Object mLock = new Object();
- private Instrumentation mInstrumentation;
- private Context mContext;
- private DisplayManager mDisplayManager;
- private ActivityManager mActivityManager;
- private ActivityManager.OnUidImportanceListener mUidImportanceListener;
- private CountDownLatch mLatchActivityLaunch;
- private CountDownLatch mLatchActivityCached;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private Messenger mMessenger;
- private int mPid;
- private int mUid;
-
/**
* Array of DisplayBundle. The test handler uses it to check if certain display events have
* been sent to DisplayEventActivity.
@@ -167,7 +132,7 @@ public class DisplayEventDeliveryTest {
*/
public void assertNoDisplayEvents() {
try {
- assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
+ assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
@@ -239,37 +204,17 @@ public class DisplayEventDeliveryTest {
}
@Before
- public void setUp() throws Exception {
- mInstrumentation = InstrumentationRegistry.getInstrumentation();
- mContext = mInstrumentation.getContext();
- mDisplayManager = mContext.getSystemService(DisplayManager.class);
- mLatchActivityLaunch = new CountDownLatch(1);
- mLatchActivityCached = new CountDownLatch(1);
- mActivityManager = mContext.getSystemService(ActivityManager.class);
- mUidImportanceListener = (uid, importance) -> {
- if (uid == mUid && importance == IMPORTANCE_CACHED) {
- Log.d(TAG, "Listener " + uid + " becomes " + importance);
- mLatchActivityCached.countDown();
- }
- };
- SystemUtil.runWithShellPermissionIdentity(() ->
- mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
- IMPORTANCE_CACHED));
+ public void setUp() {
+ super.setUp();
// The lock is not functionally necessary but eliminates lint error messages.
synchronized (mLock) {
mDisplayBundles = new SparseArray<>();
}
- mHandlerThread = new HandlerThread("handler");
- mHandlerThread.start();
- mHandler = new TestHandler(mHandlerThread.getLooper());
- mMessenger = new Messenger(mHandler);
- mPid = 0;
}
@After
public void tearDown() throws Exception {
- mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
- mHandlerThread.quitSafely();
+ super.tearDown();
synchronized (mLock) {
for (int i = 0; i < mDisplayBundles.size(); i++) {
DisplayBundle bundle = mDisplayBundles.valueAt(i);
@@ -278,7 +223,31 @@ public class DisplayEventDeliveryTest {
}
mDisplayBundles.clear();
}
- SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE);
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected Handler getHandler(Looper looper) {
+ return new TestHandler(looper);
+ }
+
+ @Override
+ protected String getTestPackage() {
+ return TEST_PACKAGE;
+ }
+
+ @Override
+ protected String getTestActivity() {
+ return TEST_ACTIVITY;
+ }
+
+ @Override
+ protected void putExtra(Intent intent) {
+ intent.putExtra(TEST_DISPLAYS, mDisplayCount);
}
/**
@@ -291,42 +260,8 @@ public class DisplayEventDeliveryTest {
}
/**
- * Return true if the freezer is enabled on this platform and if freezer notifications are
- * supported. It is not enough to test that the freezer notification feature is enabled
- * because some devices do not have the necessary kernel support.
- */
- private boolean isAppFreezerEnabled() {
- try {
- return mActivityManager.getService().isAppFreezerEnabled()
- && android.os.Flags.binderFrozenStateChangeCallback()
- && BinderProxy.isFrozenStateChangeCallbackSupported();
- } catch (Exception e) {
- Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
- return false;
- }
- }
-
- private void waitForProcessFreeze(int pid, long timeoutMs) {
- // TODO: Add a listener to monitor freezer state changes.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
- (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
- () -> mActivityManager.isProcessFrozen(pid));
- });
- }
-
- private void waitForProcessUnfreeze(int pid, long timeoutMs) {
- // TODO: Add a listener to monitor freezer state changes.
- SystemUtil.runWithShellPermissionIdentity(() -> {
- TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
- (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
- () -> !mActivityManager.isProcessFrozen(pid));
- });
- }
-
- /**
- * Create virtual displays, change their configurations and release them. The number of
- * displays is set by the {@link #mDisplays} variable.
+ * Create virtual displays, change their configurations and release them. The number of
+ * displays is set by the {@link #data()} parameter.
*/
private void testDisplayEventsInternal(boolean cached, boolean frozen) {
Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
@@ -445,110 +380,6 @@ public class DisplayEventDeliveryTest {
}
/**
- * Launch the test activity that would listen to display events. Return its process ID.
- */
- private int launchTestActivity() {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
- intent.putExtra(TEST_MESSENGER, mMessenger);
- intent.putExtra(TEST_DISPLAYS, mDisplayCount);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mContext.startActivity(intent);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- waitLatch(mLatchActivityLaunch);
-
- try {
- String cmd = "pidof " + TEST_PACKAGE;
- String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
- return Integer.parseInt(result.trim());
- } catch (IOException e) {
- fail("failed to get pid of test package");
- return 0;
- } catch (NumberFormatException e) {
- fail("failed to parse pid " + e);
- return 0;
- }
- }
-
- /**
- * Bring the test activity back to top
- */
- private void bringTestActivityTop() {
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mContext.startActivity(intent);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- }
-
- /**
- * Bring the test activity into cached mode by launching another 2 apps
- */
- private void makeTestActivityCached() {
- // Launch another activity to bring the test activity into background
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setClass(mContext, SimpleActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
-
- // Launch another activity to bring the test activity into cached mode
- Intent intent2 = new Intent(Intent.ACTION_MAIN);
- intent2.setClass(mContext, SimpleActivity2.class);
- intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- SystemUtil.runWithShellPermissionIdentity(
- () -> {
- mInstrumentation.startActivitySync(intent);
- mInstrumentation.startActivitySync(intent2);
- },
- android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
- waitLatch(mLatchActivityCached);
- }
-
- // Sleep, ignoring interrupts.
- private void pause(int s) {
- try { Thread.sleep(s * 1000); } catch (Exception e) { }
- }
-
- /**
- * Freeze the test activity.
- */
- private void makeTestActivityFrozen(int pid) {
- // The delay here is meant to allow pending binder transactions to drain. A process
- // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
- // process more than a few times will result in the system killing the process.
- pause(5);
- try {
- String cmd = "am freeze --sticky ";
- SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
- } catch (IOException e) {
- fail(e.toString());
- }
- // Wait for the freeze to complete in the kernel and for the frozen process
- // notification to settle out.
- waitForProcessFreeze(pid, 5 * 1000);
- }
-
- /**
- * Freeze the test activity.
- */
- private void makeTestActivityUnfrozen(int pid) {
- try {
- String cmd = "am unfreeze --sticky ";
- SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
- } catch (IOException e) {
- fail(e.toString());
- }
- // Wait for the freeze to complete in the kernel and for the frozen process
- // notification to settle out.
- waitForProcessUnfreeze(pid, 5 * 1000);
- }
-
- /**
* Create a virtual display
*
* @param name The name of the new virtual display
@@ -560,15 +391,4 @@ public class DisplayEventDeliveryTest {
VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
/* flags: a public virtual display that another app can access */);
}
-
- /**
- * Wait for CountDownLatch with timeout
- */
- private void waitLatch(CountDownLatch latch) {
- try {
- latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- }
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java b/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java
new file mode 100644
index 000000000000..2911b9bb35c7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/EventDeliveryTestBase.java
@@ -0,0 +1,259 @@
+/*
+ * 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.display;
+
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.BinderProxy;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Messenger;
+import android.platform.test.annotations.AppModeSdkSandbox;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
+public abstract class EventDeliveryTestBase {
+ protected static final int MESSAGE_LAUNCHED = 1;
+ protected static final int MESSAGE_CALLBACK = 2;
+
+ protected static final long EVENT_TIMEOUT_MSEC = 100;
+ protected static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;
+
+ private static final String TEST_MESSENGER = "MESSENGER";
+
+ private Instrumentation mInstrumentation;
+ private Context mContext;
+ protected DisplayManager mDisplayManager;
+ private ActivityManager mActivityManager;
+ private ActivityManager.OnUidImportanceListener mUidImportanceListener;
+ protected CountDownLatch mLatchActivityLaunch;
+ private CountDownLatch mLatchActivityCached;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private Messenger mMessenger;
+ protected int mPid;
+ protected int mUid;
+
+ protected abstract String getTag();
+
+ protected abstract Handler getHandler(Looper looper);
+
+ protected abstract String getTestPackage();
+
+ protected abstract String getTestActivity();
+
+ protected abstract void putExtra(Intent intent);
+
+ protected void setUp() {
+ mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ mLatchActivityLaunch = new CountDownLatch(1);
+ mLatchActivityCached = new CountDownLatch(1);
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mUidImportanceListener = (uid, importance) -> {
+ if (uid == mUid && importance == IMPORTANCE_CACHED) {
+ Log.d(getTag(), "Listener " + uid + " becomes " + importance);
+ mLatchActivityCached.countDown();
+ }
+ };
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
+ IMPORTANCE_CACHED));
+ mHandlerThread = new HandlerThread("handler");
+ mHandlerThread.start();
+ mHandler = getHandler(mHandlerThread.getLooper());
+ mMessenger = new Messenger(mHandler);
+ mPid = 0;
+ }
+
+ protected void tearDown() throws Exception {
+ mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
+ mHandlerThread.quitSafely();
+ SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + getTestPackage());
+ }
+
+ /**
+ * Return true if the freezer is enabled on this platform and if freezer notifications are
+ * supported. It is not enough to test that the freezer notification feature is enabled
+ * because some devices do not have the necessary kernel support.
+ */
+ protected boolean isAppFreezerEnabled() {
+ try {
+ return ActivityManager.getService().isAppFreezerEnabled()
+ && android.os.Flags.binderFrozenStateChangeCallback()
+ && BinderProxy.isFrozenStateChangeCallbackSupported();
+ } catch (Exception e) {
+ Log.e(getTag(), "isAppFreezerEnabled() failed: " + e);
+ return false;
+ }
+ }
+
+ private void waitForProcessFreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil(
+ "Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ private void waitForProcessUnfreeze(int pid, long timeoutMs) {
+ // TODO: Add a listener to monitor freezer state changes.
+ SystemUtil.runWithShellPermissionIdentity(() -> {
+ TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
+ (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
+ () -> !mActivityManager.isProcessFrozen(pid));
+ });
+ }
+
+ /**
+ * Launch the test activity that would listen to events. Return its process ID.
+ */
+ protected int launchTestActivity() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getTestPackage(), getTestActivity());
+ intent.putExtra(TEST_MESSENGER, mMessenger);
+ putExtra(intent);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mContext.startActivity(intent);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ waitLatch(mLatchActivityLaunch);
+
+ try {
+ String cmd = "pidof " + getTestPackage();
+ String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
+ return Integer.parseInt(result.trim());
+ } catch (IOException e) {
+ fail("failed to get pid of test package");
+ return 0;
+ } catch (NumberFormatException e) {
+ fail("failed to parse pid " + e);
+ return 0;
+ }
+ }
+
+ /**
+ * Bring the test activity back to top
+ */
+ protected void bringTestActivityTop() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClassName(getTestPackage(), getTestActivity());
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mContext.startActivity(intent);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ }
+
+
+ /**
+ * Bring the test activity into cached mode by launching another 2 apps
+ */
+ protected void makeTestActivityCached() {
+ // Launch another activity to bring the test activity into background
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.setClass(mContext, SimpleActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+
+ // Launch another activity to bring the test activity into cached mode
+ Intent intent2 = new Intent(Intent.ACTION_MAIN);
+ intent2.setClass(mContext, SimpleActivity2.class);
+ intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> {
+ mInstrumentation.startActivitySync(intent);
+ mInstrumentation.startActivitySync(intent2);
+ },
+ android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
+ waitLatch(mLatchActivityCached);
+ }
+
+ // Sleep, ignoring interrupts.
+ private void pause(int s) {
+ try {
+ Thread.sleep(s * 1000L);
+ } catch (Exception ignored) { }
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ protected void makeTestActivityFrozen(int pid) {
+ // The delay here is meant to allow pending binder transactions to drain. A process
+ // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
+ // process more than a few times will result in the system killing the process.
+ pause(5);
+ try {
+ String cmd = "am freeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + getTestPackage());
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessFreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Freeze the test activity.
+ */
+ protected void makeTestActivityUnfrozen(int pid) {
+ try {
+ String cmd = "am unfreeze --sticky ";
+ SystemUtil.runShellCommand(mInstrumentation, cmd + getTestPackage());
+ } catch (IOException e) {
+ fail(e.toString());
+ }
+ // Wait for the freeze to complete in the kernel and for the frozen process
+ // notification to settle out.
+ waitForProcessUnfreeze(pid, 5 * 1000);
+ }
+
+ /**
+ * Wait for CountDownLatch with timeout
+ */
+ private void waitLatch(CountDownLatch latch) {
+ try {
+ latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java b/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java
new file mode 100644
index 000000000000..5fd248dba53f
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/TopologyUpdateDeliveryTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.display;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.hardware.display.DisplayTopology;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests that applications can receive topology updates correctly.
+ */
+public class TopologyUpdateDeliveryTest extends EventDeliveryTestBase {
+ private static final String TAG = TopologyUpdateDeliveryTest.class.getSimpleName();
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ private static final String TEST_PACKAGE = "com.android.servicestests.apps.topologytestapp";
+ private static final String TEST_ACTIVITY = TEST_PACKAGE + ".TopologyUpdateActivity";
+
+ // Topology updates we expect to receive before timeout
+ private final LinkedBlockingQueue<DisplayTopology> mExpectations = new LinkedBlockingQueue<>();
+
+ /**
+ * Add the received topology update from the test activity to the queue
+ *
+ * @param topology The corresponding topology update
+ */
+ private void addTopologyUpdate(DisplayTopology topology) {
+ Log.d(TAG, "Received " + topology);
+ mExpectations.offer(topology);
+ }
+
+ /**
+ * Assert that there isn't any unexpected display event from the test activity
+ */
+ private void assertNoTopologyUpdates() {
+ try {
+ assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Wait for the expected topology update from the test activity
+ *
+ * @param expect The expected topology update
+ */
+ private void waitTopologyUpdate(DisplayTopology expect) {
+ while (true) {
+ try {
+ DisplayTopology update = mExpectations.poll(TEST_FAILURE_TIMEOUT_MSEC,
+ TimeUnit.MILLISECONDS);
+ assertNotNull(update);
+ if (expect.equals(update)) {
+ Log.d(TAG, "Found " + update);
+ return;
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private class TestHandler extends Handler {
+ TestHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ switch (msg.what) {
+ case MESSAGE_LAUNCHED:
+ mPid = msg.arg1;
+ mUid = msg.arg2;
+ Log.d(TAG, "Launched " + mPid + " " + mUid);
+ mLatchActivityLaunch.countDown();
+ break;
+ case MESSAGE_CALLBACK:
+ DisplayTopology topology = (DisplayTopology) msg.obj;
+ Log.d(TAG, "Callback " + topology);
+ addTopologyUpdate(topology);
+ break;
+ default:
+ fail("Unexpected value: " + msg.what);
+ break;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() {
+ super.setUp();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Override
+ protected String getTag() {
+ return TAG;
+ }
+
+ @Override
+ protected Handler getHandler(Looper looper) {
+ return new TestHandler(looper);
+ }
+
+ @Override
+ protected String getTestPackage() {
+ return TEST_PACKAGE;
+ }
+
+ @Override
+ protected String getTestActivity() {
+ return TEST_ACTIVITY;
+ }
+
+ @Override
+ protected void putExtra(Intent intent) { }
+
+ private void testTopologyUpdateInternal(boolean cached, boolean frozen) {
+ Log.d(TAG, "Start test testTopologyUpdate " + cached + " " + frozen);
+ // Launch activity and start listening to topology updates
+ int pid = launchTestActivity();
+
+ // The test activity in cached or frozen mode won't receive the pending topology updates.
+ if (cached) {
+ makeTestActivityCached();
+ }
+ if (frozen) {
+ makeTestActivityFrozen(pid);
+ }
+
+ // Change the topology
+ int primaryDisplayId = 3;
+ DisplayTopology.TreeNode root = new DisplayTopology.TreeNode(primaryDisplayId,
+ /* width= */ 600, /* height= */ 400, DisplayTopology.TreeNode.POSITION_LEFT,
+ /* offset= */ 0);
+ DisplayTopology.TreeNode child = new DisplayTopology.TreeNode(/* displayId= */ 1,
+ /* width= */ 800, /* height= */ 600, DisplayTopology.TreeNode.POSITION_LEFT,
+ /* offset= */ 0);
+ root.addChild(child);
+ DisplayTopology topology = new DisplayTopology(root, primaryDisplayId);
+ mDisplayManager.setDisplayTopology(topology);
+
+ if (cached || frozen) {
+ assertNoTopologyUpdates();
+ } else {
+ waitTopologyUpdate(topology);
+ }
+
+ // Unfreeze the test activity, if it was frozen.
+ if (frozen) {
+ makeTestActivityUnfrozen(pid);
+ }
+
+ if (cached || frozen) {
+ // Always ensure the test activity is not cached.
+ bringTestActivityTop();
+
+ // The test activity becomes non-cached and should receive the pending topology updates
+ waitTopologyUpdate(topology);
+ }
+ }
+
+ @Test
+ @RequiresFlagsEnabled(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyUpdate() {
+ testTopologyUpdateInternal(false, false);
+ }
+
+ /**
+ * The app is moved to cached and the test verifies that no updates are delivered to the cached
+ * app.
+ */
+ @Test
+ @RequiresFlagsEnabled(com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY)
+ public void testTopologyUpdateCached() {
+ testTopologyUpdateInternal(true, false);
+ }
+
+ /**
+ * The app is frozen and the test verifies that no updates are delivered to the frozen app.
+ */
+ @RequiresFlagsEnabled({com.android.server.am.Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN,
+ com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY})
+ @Test
+ public void testTopologyUpdateFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testTopologyUpdateInternal(false, true);
+ }
+
+ /**
+ * The app is cached and frozen and the test verifies that no updates are delivered to the app.
+ */
+ @RequiresFlagsEnabled({com.android.server.am.Flags.FLAG_DEFER_DISPLAY_EVENTS_WHEN_FROZEN,
+ com.android.server.display.feature.flags.Flags.FLAG_DISPLAY_TOPOLOGY})
+ @Test
+ public void testTopologyUpdateCachedFrozen() {
+ assumeTrue(isAppFreezerEnabled());
+ testTopologyUpdateInternal(true, true);
+ }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
index 1f3f19fa3ea8..218728541774 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -89,6 +89,39 @@ class AppRequestObserverTest {
assertThat(renderRateVote).isEqualTo(testCase.expectedRenderRateVote)
}
+ @Test
+ fun testAppRequestVote_externalDisplay() {
+ val displayModeDirector = DisplayModeDirector(
+ context, testHandler, mockInjector, mockFlags, mockDisplayDeviceConfigProvider)
+ val modes = arrayOf(
+ Display.Mode(1, 1000, 1000, 60f),
+ Display.Mode(2, 1000, 1000, 90f),
+ )
+
+ displayModeDirector.injectAppSupportedModesByDisplay(
+ SparseArray<Array<Display.Mode>>().apply {
+ append(Display.DEFAULT_DISPLAY, modes)
+ })
+ displayModeDirector.injectDefaultModeByDisplay(SparseArray<Display.Mode>().apply {
+ append(Display.DEFAULT_DISPLAY, modes[0])
+ })
+ displayModeDirector.addExternalDisplayId(Display.DEFAULT_DISPLAY)
+
+ displayModeDirector.appRequestObserver.setAppRequest(Display.DEFAULT_DISPLAY, 1, 0f, 0f, 0f)
+
+ val baseModeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE)
+ assertThat(baseModeVote).isEqualTo(BaseModeRefreshRateVote(60f))
+
+ val sizeVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_SIZE)
+ assertThat(sizeVote).isNull()
+
+ val renderRateVote = displayModeDirector.getVote(Display.DEFAULT_DISPLAY,
+ Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE)
+ assertThat(renderRateVote).isNull()
+ }
+
enum class AppRequestTestCase(
val ignoreRefreshRateRequest: Boolean,
val modeId: Int,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
index 3289d70b89ac..fe4baeb80ee7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -79,7 +79,8 @@ import java.util.ArrayList;
public class ApplicationStartInfoTest {
private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
- private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
+ private static final ComponentName COMPONENT =
+ new ComponentName("com.android.test", "com.android.test.Foo");
private static final int APP_1_UID = 10123;
private static final int APP_1_PID_1 = 12345;
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index d702cae248a9..64e6d323bdfd 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -31,6 +31,7 @@ android_test {
"test-apps/SuspendTestApp/src/**/*.java",
"test-apps/DisplayManagerTestApp/src/**/*.java",
+ "test-apps/TopologyTestApp/src/**/*.java",
],
static_libs: [
@@ -92,7 +93,6 @@ android_test {
"net_flags_lib",
"CtsVirtualDeviceCommonLib",
"com_android_server_accessibility_flags_lib",
- "locksettings_flags_lib",
] + select(soong_config_variable("ANDROID", "release_crashrecovery_module"), {
"true": ["service-crashrecovery-pre-jarjar"],
default: [],
@@ -141,6 +141,7 @@ android_test {
data: [
":DisplayManagerTestApp",
+ ":TopologyTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 5298251b79f7..9a4983482522 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -36,6 +36,7 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="DisplayManagerTestApp.apk" />
+ <option name="test-file-name" value="TopologyTestApp.apk" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
<option name="test-file-name" value="SuspendTestApp.apk" />
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
index e0bc3e76f31d..457d8a96fea4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -87,6 +87,9 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.hardware.input.InputManagerGlobal;
import android.hardware.input.KeyGestureEvent;
import android.net.Uri;
import android.os.Build;
@@ -237,6 +240,9 @@ public class AccessibilityManagerServiceTest {
@Mock
private HearingDevicePhoneCallNotificationController
mMockHearingDevicePhoneCallNotificationController;
+ @Mock
+ private IInputManager mMockInputManagerService;
+ private InputManagerGlobal.TestSession mInputManagerTestSession;
@Spy private IUserInitializationCompleteCallback mUserInitializationCompleteCallback;
@Captor private ArgumentCaptor<Intent> mIntentArgumentCaptor;
private IAccessibilityManager mA11yManagerServiceOnDevice;
@@ -270,6 +276,10 @@ public class AccessibilityManagerServiceTest {
mInputFilter = mock(FakeInputFilter.class);
mTestableContext.addMockSystemService(DevicePolicyManager.class, mDevicePolicyManager);
+ mInputManagerTestSession = InputManagerGlobal.createTestSession(mMockInputManagerService);
+ InputManager mockInputManager = new InputManager(mTestableContext);
+ mTestableContext.addMockSystemService(InputManager.class, mockInputManager);
+
when(mMockPackageManagerInternal.getSystemUiServiceComponent()).thenReturn(
new ComponentName("com.android.systemui", "com.android.systemui.SystemUIService"));
when(mMockPackageManagerInternal.getPackageUid(eq("com.android.systemui"), anyLong(),
@@ -334,6 +344,9 @@ public class AccessibilityManagerServiceTest {
FieldSetter.setField(
am, AccessibilityManager.class.getDeclaredField("mService"),
mA11yManagerServiceOnDevice);
+ if (mInputManagerTestSession != null) {
+ mInputManagerTestSession.close();
+ }
}
private void setupAccessibilityServiceConnection(int serviceInfoFlag) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index 99c922ca30c4..df77866b5e7f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -866,6 +867,23 @@ public class AutoclickControllerTest {
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void scrollPanelController_directionalButtonsHideIndicator() {
+ injectFakeMouseActionHoverMoveEvent();
+
+ // Create a spy on the real object to verify method calls.
+ AutoclickIndicatorView spyIndicatorView = spy(mController.mAutoclickIndicatorView);
+ mController.mAutoclickIndicatorView = spyIndicatorView;
+
+ // Simulate hover on direction button.
+ mController.mScrollPanelController.onHoverButtonChange(
+ AutoclickScrollPanel.DIRECTION_UP, true);
+
+ // Verify clearIndicator was called.
+ verify(spyIndicatorView).clearIndicator();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void hoverOnAutoclickPanel_rightClickType_forceTriggerLeftClick() {
MotionEventCaptor motionEventCaptor = new MotionEventCaptor();
mController.setNext(motionEventCaptor);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java
index d94faec4cf01..49eb63b2c261 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java
@@ -28,10 +28,6 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.graphics.PointF;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.view.Display;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -54,9 +50,6 @@ import java.util.List;
*/
public class TwoFingersDownOrSwipeTest {
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private static final float DEFAULT_X = 100f;
private static final float DEFAULT_Y = 100f;
@@ -94,22 +87,6 @@ public class TwoFingersDownOrSwipeTest {
}
@Test
- @RequiresFlagsDisabled(android.view.accessibility.Flags.FLAG_COPY_EVENTS_FOR_GESTURE_DETECTION)
- public void sendTwoFingerDownEvent_onGestureCompleted_withoutCopiedEvents() {
- final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
- new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
-
- for (MotionEvent event : downEvents) {
- mGesturesObserver.onMotionEvent(event, event, 0);
- }
-
- verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted(
- MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE, downEvents.get(1),
- downEvents.get(1), 0);
- }
-
- @Test
- @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_COPY_EVENTS_FOR_GESTURE_DETECTION)
public void sendTwoFingerDownEvent_onGestureCompleted() {
final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY,
new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10));
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java
new file mode 100644
index 000000000000..f252a9848bbf
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioVolumeChangeHandlerTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.audio;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.media.INativeAudioVolumeGroupCallback;
+import android.media.audio.common.AudioVolumeGroupChangeEvent;
+import android.media.audiopolicy.IAudioVolumeChangeDispatcher;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioVolumeChangeHandlerTest {
+ private static final long DEFAULT_TIMEOUT_MS = 1000;
+
+ private AudioSystemAdapter mSpyAudioSystem;
+
+ AudioVolumeChangeHandler mAudioVolumeChangedHandler;
+
+ private final IAudioVolumeChangeDispatcher.Stub mMockDispatcher =
+ mock(IAudioVolumeChangeDispatcher.Stub.class);
+
+ @Before
+ public void setUp() {
+ mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
+ when(mMockDispatcher.asBinder()).thenReturn(mock(IBinder.class));
+ mAudioVolumeChangedHandler = new AudioVolumeChangeHandler(mSpyAudioSystem);
+ }
+
+ @Test
+ public void registerListener_withInvalidCallback() {
+ IAudioVolumeChangeDispatcher.Stub nullCb = null;
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ mAudioVolumeChangedHandler.registerListener(nullCb);
+ });
+
+ assertWithMessage("Exception for invalid registration").that(thrown).hasMessageThat()
+ .contains("Volume group callback");
+ }
+
+ @Test
+ public void unregisterListener_withInvalidCallback() {
+ IAudioVolumeChangeDispatcher.Stub nullCb = null;
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+
+ NullPointerException thrown = assertThrows(NullPointerException.class, () -> {
+ mAudioVolumeChangedHandler.unregisterListener(nullCb);
+ });
+
+ assertWithMessage("Exception for invalid un-registration").that(thrown).hasMessageThat()
+ .contains("Volume group callback");
+ }
+
+ @Test
+ public void registerListener() {
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+
+ verify(mSpyAudioSystem).registerAudioVolumeGroupCallback(any());
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged() throws Exception {
+ mAudioVolumeChangedHandler.registerListener(mMockDispatcher);
+ AudioVolumeGroupChangeEvent volEvent = new AudioVolumeGroupChangeEvent();
+ volEvent.groupId = 666;
+ volEvent.flags = AudioVolumeGroupChangeEvent.VOLUME_FLAG_FROM_KEY;
+
+ captureRegisteredNativeCallback().onAudioVolumeGroupChanged(volEvent);
+
+ verify(mMockDispatcher, timeout(DEFAULT_TIMEOUT_MS)).onAudioVolumeGroupChanged(
+ eq(volEvent.groupId), eq(volEvent.flags));
+ }
+
+ @Test
+ public void onAudioVolumeGroupChanged_withMultipleCallback() throws Exception {
+ int callbackCount = 10;
+ List<IAudioVolumeChangeDispatcher.Stub> validCbs =
+ new ArrayList<IAudioVolumeChangeDispatcher.Stub>();
+ for (int i = 0; i < callbackCount; i++) {
+ IAudioVolumeChangeDispatcher.Stub cb = mock(IAudioVolumeChangeDispatcher.Stub.class);
+ when(cb.asBinder()).thenReturn(mock(IBinder.class));
+ validCbs.add(cb);
+ }
+ for (IAudioVolumeChangeDispatcher.Stub cb : validCbs) {
+ mAudioVolumeChangedHandler.registerListener(cb);
+ }
+ AudioVolumeGroupChangeEvent volEvent = new AudioVolumeGroupChangeEvent();
+ volEvent.groupId = 666;
+ volEvent.flags = AudioVolumeGroupChangeEvent.VOLUME_FLAG_FROM_KEY;
+ captureRegisteredNativeCallback().onAudioVolumeGroupChanged(volEvent);
+
+ for (IAudioVolumeChangeDispatcher.Stub cb : validCbs) {
+ verify(cb, timeout(DEFAULT_TIMEOUT_MS)).onAudioVolumeGroupChanged(
+ eq(volEvent.groupId), eq(volEvent.flags));
+ }
+ }
+
+ private INativeAudioVolumeGroupCallback captureRegisteredNativeCallback() {
+ ArgumentCaptor<INativeAudioVolumeGroupCallback> nativeAudioVolumeGroupCallbackCaptor =
+ ArgumentCaptor.forClass(INativeAudioVolumeGroupCallback.class);
+ verify(mSpyAudioSystem, timeout(DEFAULT_TIMEOUT_MS))
+ .registerAudioVolumeGroupCallback(nativeAudioVolumeGroupCallbackCaptor.capture());
+ return nativeAudioVolumeGroupCallbackCaptor.getValue();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index c50c62323212..a1f73170e549 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -816,14 +816,14 @@ public class DevicePolicyManagerTest extends DpmTestBase {
MockUtils.checkIntentAction(
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED),
MockUtils.checkUserHandle(CALLER_USER_HANDLE),
- isNull(String.class),
+ isNull(),
eq(AppOpsManager.OP_NONE),
any(Bundle.class),
any(BroadcastReceiver.class),
eq(dpms.mHandler),
eq(Activity.RESULT_OK),
- isNull(String.class),
- isNull(Bundle.class));
+ isNull(),
+ isNull());
assertThat(dpm.isAdminActiveAsUser(admin1, CALLER_USER_HANDLE)).isFalse();
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
@@ -873,14 +873,14 @@ public class DevicePolicyManagerTest extends DpmTestBase {
MockUtils.checkIntentAction(
DeviceAdminReceiver.ACTION_DEVICE_ADMIN_DISABLED),
MockUtils.checkUserHandle(CALLER_USER_HANDLE),
- isNull(String.class),
+ isNull(),
eq(AppOpsManager.OP_NONE),
any(Bundle.class),
any(BroadcastReceiver.class),
eq(dpms.mHandler),
eq(Activity.RESULT_OK),
- isNull(String.class),
- isNull(Bundle.class));
+ isNull(),
+ isNull());
assertThat(dpm.isAdminActiveAsUser(admin1, CALLER_USER_HANDLE)).isFalse();
verify(getServices().usageStatsManagerInternal).setActiveAdminApps(
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 43b1ec393bf6..87cd1560509c 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
@@ -19,7 +19,9 @@ package com.android.server.location.contexthub;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -29,6 +31,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.contexthub.EndpointInfo;
import android.hardware.contexthub.ErrorCode;
+import android.hardware.contexthub.HubEndpoint;
import android.hardware.contexthub.HubEndpointInfo;
import android.hardware.contexthub.HubEndpointInfo.HubEndpointIdentifier;
import android.hardware.contexthub.HubMessage;
@@ -385,6 +388,49 @@ public class ContextHubEndpointTest {
unregisterExampleEndpoint(endpoint);
}
+ @Test
+ public void testUnreliableMessageFailureClosesSession() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ doThrow(new RemoteException("Intended exception in test"))
+ .when(mMockCallback)
+ .onMessageReceived(anyInt(), any(HubMessage.class));
+ mEndpointManager.onMessageReceived(sessionId, SAMPLE_UNRELIABLE_MESSAGE);
+ ArgumentCaptor<HubMessage> messageCaptor = ArgumentCaptor.forClass(HubMessage.class);
+ verify(mMockCallback).onMessageReceived(eq(sessionId), messageCaptor.capture());
+ assertThat(messageCaptor.getValue()).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE);
+
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.UNSPECIFIED);
+ verify(mMockCallback).onSessionClosed(sessionId, HubEndpoint.REASON_FAILURE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
+ @Test
+ public void testSendUnreliableMessageFailureClosesSession() throws RemoteException {
+ IContextHubEndpoint endpoint = registerExampleEndpoint();
+ int sessionId = openTestSession(endpoint);
+
+ doThrow(new RemoteException("Intended exception in test"))
+ .when(mMockEndpointCommunications)
+ .sendMessageToEndpoint(anyInt(), any(Message.class));
+ endpoint.sendMessage(sessionId, SAMPLE_UNRELIABLE_MESSAGE, /* callback= */ null);
+ ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mMockEndpointCommunications)
+ .sendMessageToEndpoint(eq(sessionId), messageCaptor.capture());
+ Message message = messageCaptor.getValue();
+ assertThat(message.type).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE.getMessageType());
+ assertThat(message.content).isEqualTo(SAMPLE_UNRELIABLE_MESSAGE.getMessageBody());
+
+ verify(mMockEndpointCommunications).closeEndpointSession(sessionId, Reason.UNSPECIFIED);
+ verify(mMockCallback).onSessionClosed(sessionId, HubEndpoint.REASON_FAILURE);
+ assertThat(mEndpointManager.getNumAvailableSessions()).isEqualTo(SESSION_ID_RANGE);
+
+ unregisterExampleEndpoint(endpoint);
+ }
+
/** A helper method to create a session and validates reliable message sending. */
private void testMessageTransactionInternal(
IContextHubEndpoint endpoint, boolean deliverMessageStatus) throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index ae781dcb834a..52c7c48cc8b7 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -60,10 +60,6 @@ import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.UserManager;
import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -74,7 +70,6 @@ import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnReb
import com.android.server.pm.UserManagerInternal;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -113,9 +108,6 @@ public class RebootEscrowManagerTests {
0x26, 0x52, 0x72, 0x63, 0x63, 0x61, 0x78, 0x23,
};
- @Rule
- public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
private Context mContext;
private UserManager mUserManager;
private UserManagerInternal mUserManagerInternal;
@@ -847,53 +839,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsDisabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
- public void loadRebootEscrowDataIfAvailable_ServerBasedIoError_RetryFailure() throws Exception {
- setServerBasedRebootEscrowProvider();
-
- when(mInjected.getBootCount()).thenReturn(0);
- RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
- mService.setRebootEscrowListener(mockListener);
- mService.prepareRebootEscrow();
-
- clearInvocations(mServiceConnection);
- callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
- verify(mockListener).onPreparedForReboot(eq(true));
- verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
-
- // Use x -> x for both wrap & unwrap functions.
- when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
- .thenAnswer(invocation -> invocation.getArgument(0));
- assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
- verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
- assertTrue(mStorage.hasRebootEscrowServerBlob());
-
- // pretend reboot happens here
- when(mInjected.getBootCount()).thenReturn(1);
- ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
- ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
- doNothing()
- .when(mInjected)
- .reportMetric(
- metricsSuccessCaptor.capture(),
- metricsErrorCodeCaptor.capture(),
- eq(2) /* Server based */,
- eq(2) /* attempt count */,
- anyInt(),
- eq(0) /* vbmeta status */,
- anyInt());
- when(mServiceConnection.unwrap(any(), anyLong())).thenThrow(IOException.class);
-
- mService.loadRebootEscrowDataIfAvailable(mHandler);
- // Sleep 5s for the retry to complete
- Thread.sleep(5 * 1000);
- assertFalse(metricsSuccessCaptor.getValue());
- assertEquals(
- Integer.valueOf(RebootEscrowManager.ERROR_NO_NETWORK),
- metricsErrorCodeCaptor.getValue());
- }
-
- @Test
public void loadRebootEscrowDataIfAvailable_ServerBased_RetrySuccess() throws Exception {
setServerBasedRebootEscrowProvider();
@@ -941,7 +886,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_waitForInternet_networkUnavailable()
throws Exception {
setServerBasedRebootEscrowProvider();
@@ -989,7 +933,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_waitForInternet_networkLost() throws Exception {
setServerBasedRebootEscrowProvider();
@@ -1044,7 +987,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_waitForInternet_networkAvailableWithDelay()
throws Exception {
setServerBasedRebootEscrowProvider();
@@ -1103,7 +1045,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_waitForInternet_timeoutExhausted()
throws Exception {
setServerBasedRebootEscrowProvider();
@@ -1163,7 +1104,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_serverBasedWaitForNetwork_retryCountExhausted()
throws Exception {
setServerBasedRebootEscrowProvider();
@@ -1219,7 +1159,6 @@ public class RebootEscrowManagerTests {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_WAIT_FOR_INTERNET_ROR)
public void loadRebootEscrowDataIfAvailable_ServerBasedWaitForInternet_RetrySuccess()
throws Exception {
setServerBasedRebootEscrowProvider();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index 88395a4889c4..071bd739072b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -306,9 +306,9 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
// This method is always called, even with PI == null.
if (resultIntent == null) {
- verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(isNull());
} else {
- verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull());
}
runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
@@ -619,7 +619,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
makeResultIntent()));
// The intent should be sent right away.
- verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull());
});
// Already pinned.
@@ -661,7 +661,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
assertTrue(request.accept());
// The intent is only sent once, so times(1).
- verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(isNull());
});
// Still pinned.
@@ -698,7 +698,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
makeResultIntent()));
// The intent should be sent right away.
- verify(mServiceContext, times(1)).sendIntentSender(notNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(notNull());
});
// Already pinned.
@@ -742,7 +742,7 @@ public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
assertTrue(request.accept());
// The intent is only sent once, so times(1).
- verify(mServiceContext, times(1)).sendIntentSender(isNull(IntentSender.class));
+ verify(mServiceContext, times(1)).sendIntentSender(isNull());
});
// Still pinned.
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 952d8fa47a34..09acfddacf03 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -51,6 +51,7 @@ import android.os.IThermalStatusListener;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.Temperature;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -78,6 +79,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server
@@ -117,7 +119,8 @@ public class ThermalManagerServiceTest {
*/
private class ThermalHalFake extends ThermalHalWrapper {
private static final int INIT_STATUS = Temperature.THROTTLING_NONE;
- private List<Temperature> mTemperatureList = new ArrayList<>();
+ private final List<Temperature> mTemperatureList = new ArrayList<>();
+ private AtomicInteger mGetCurrentTemperaturesCalled = new AtomicInteger();
private List<CoolingDevice> mCoolingDeviceList = new ArrayList<>();
private List<TemperatureThreshold> mTemperatureThresholdList = initializeThresholds();
@@ -173,6 +176,7 @@ public class ThermalManagerServiceTest {
mTemperatureList.add(mUsbPort);
mCoolingDeviceList.add(mCpu);
mCoolingDeviceList.add(mGpu);
+ mGetCurrentTemperaturesCalled.set(0);
}
void enableForecastSkinTemperature() {
@@ -188,14 +192,24 @@ public class ThermalManagerServiceTest {
mForecastSkinTemperaturesError = true;
}
+ void updateTemperatureList(Temperature... temperatures) {
+ synchronized (mTemperatureList) {
+ mTemperatureList.clear();
+ mTemperatureList.addAll(Arrays.asList(temperatures));
+ }
+ }
+
@Override
protected List<Temperature> getCurrentTemperatures(boolean shouldFilter, int type) {
List<Temperature> ret = new ArrayList<>();
- for (Temperature temperature : mTemperatureList) {
- if (shouldFilter && type != temperature.getType()) {
- continue;
+ synchronized (mTemperatureList) {
+ mGetCurrentTemperaturesCalled.incrementAndGet();
+ for (Temperature temperature : mTemperatureList) {
+ if (shouldFilter && type != temperature.getType()) {
+ continue;
+ }
+ ret.add(temperature);
}
- ret.add(temperature);
}
return ret;
}
@@ -407,7 +421,7 @@ public class ThermalManagerServiceTest {
Thread.sleep(CALLBACK_TIMEOUT_MILLI_SEC);
resetListenerMock();
int status = Temperature.THROTTLING_SEVERE;
- mFakeHal.mTemperatureList = new ArrayList<>();
+ mFakeHal.updateTemperatureList();
// Should not notify on non-skin type
Temperature newBattery = new Temperature(37, Temperature.TYPE_BATTERY, "batt", status);
@@ -537,6 +551,42 @@ public class ThermalManagerServiceTest {
}
@Test
+ @DisableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST})
+ public void testGetThermalHeadroom_handlerUpdateTemperatures()
+ throws RemoteException, InterruptedException {
+ // test that handler will at least enqueue one message to periodically read temperatures
+ // even if there is sample seeded from HAL temperature callback
+ String temperatureName = "skin1";
+ Temperature temperature = new Temperature(100, Temperature.TYPE_SKIN, temperatureName,
+ Temperature.THROTTLING_NONE);
+ mFakeHal.mCallback.onTemperatureChanged(temperature);
+ float headroom = mService.mService.getThermalHeadroom(0);
+ // the callback temperature 100C (headroom > 1.0f) sample should have been appended by the
+ // immediately scheduled fake HAL current temperatures read (mSkin1, mSkin2), and because
+ // there are less samples for prediction, the latest temperature mSkin1 is used to calculate
+ // headroom (mSkin2 has no threshold), which is 0.6f (28C vs threshold 40C).
+ assertEquals(0.6f, headroom, 0.01f);
+ // one called by service onActivityManagerReady, one called by handler on headroom call
+ assertEquals(2, mFakeHal.mGetCurrentTemperaturesCalled.get());
+ // periodic read should update the samples history, so the headroom should increase 0.1f
+ // as current temperature goes up by 3C every 1100ms.
+ for (int i = 1; i < 5; i++) {
+ Temperature newTemperature = new Temperature(mFakeHal.mSkin1.getValue() + 3 * i,
+ Temperature.TYPE_SKIN,
+ temperatureName,
+ Temperature.THROTTLING_NONE);
+ mFakeHal.updateTemperatureList(newTemperature);
+ // wait for handler to update temperature
+ Thread.sleep(1100);
+ // assert that only one callback was scheduled to query HAL when making multiple
+ // headroom calls
+ assertEquals(2 + i, mFakeHal.mGetCurrentTemperaturesCalled.get());
+ headroom = mService.mService.getThermalHeadroom(0);
+ assertEquals(0.6f + 0.1f * i, headroom, 0.01f);
+ }
+ }
+
+ @Test
@EnableFlags({Flags.FLAG_ALLOW_THERMAL_HAL_SKIN_FORECAST})
public void testGetThermalHeadroom_halForecast() throws RemoteException {
mFakeHal.mForecastSkinTemperaturesCalled = 0;
diff --git a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
index 339bac4f768b..d6b3fecb487c 100644
--- a/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/advancedprotection/AdvancedProtectionServiceTest.java
@@ -45,6 +45,8 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
import java.util.concurrent.atomic.AtomicBoolean;
@SuppressLint("VisibleForTests")
@@ -66,17 +68,28 @@ public class AdvancedProtectionServiceTest {
mPermissionEnforcer.grant(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
mStore = new AdvancedProtectionService.AdvancedProtectionStore(mContext) {
+ private Map<String, Integer> mStoredValues = new HashMap<>();
private boolean mEnabled = false;
@Override
- boolean retrieve() {
+ boolean retrieveAdvancedProtectionModeEnabled() {
return mEnabled;
}
@Override
- void store(boolean enabled) {
+ void storeAdvancedProtectionModeEnabled(boolean enabled) {
this.mEnabled = enabled;
}
+
+ @Override
+ void storeInt(String key, int value) {
+ mStoredValues.put(key, value);
+ }
+
+ @Override
+ int retrieveInt(String key, int defaultValue) {
+ return mStoredValues.getOrDefault(key, defaultValue);
+ }
};
mLooper = new TestLooper();
@@ -316,6 +329,18 @@ public class AdvancedProtectionServiceTest {
}
@Test
+ public void testUsbDataProtection_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.isUsbDataProtectionEnabled());
+ }
+
+ @Test
+ public void testSetUsbDataProtection_withoutPermission() {
+ mPermissionEnforcer.revoke(Manifest.permission.MANAGE_ADVANCED_PROTECTION_MODE);
+ assertThrows(SecurityException.class, () -> mService.setUsbDataProtectionEnabled(true));
+ }
+
+ @Test
public void testRegisterCallback_withoutPermission() {
mPermissionEnforcer.revoke(Manifest.permission.QUERY_ADVANCED_PROTECTION_MODE);
assertThrows(SecurityException.class, () -> mService.registerAdvancedProtectionCallback(
diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
index 148c96850d34..6d682ccef98d 100644
--- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java
@@ -36,6 +36,7 @@ import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.nullable;
@@ -69,16 +70,20 @@ import android.os.Binder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.service.quicksettings.TileService;
import android.testing.TestableContext;
+import android.util.Pair;
import androidx.test.InstrumentationRegistry;
+import com.android.internal.statusbar.DisableStates;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBar;
import com.android.server.LocalServices;
import com.android.server.policy.GlobalActionsProvider;
import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.systemui.shared.Flags;
import libcore.junit.util.compat.CoreCompatChangeRule;
@@ -105,6 +110,7 @@ public class StatusBarManagerServiceTest {
TEST_SERVICE);
private static final CharSequence APP_NAME = "AppName";
private static final CharSequence TILE_LABEL = "Tile label";
+ private static final int SECONDARY_DISPLAY_ID = 2;
@Rule
public final TestableContext mContext =
@@ -749,6 +755,40 @@ public class StatusBarManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisableForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE_MASK & DISABLE_BACK;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg);
+
+ ArgumentCaptor<DisableStates> disableStatesCaptor = ArgumentCaptor.forClass(
+ DisableStates.class);
+ verify(mMockStatusBar).disableForAllDisplays(disableStatesCaptor.capture());
+ DisableStates capturedDisableStates = disableStatesCaptor.getValue();
+ assertTrue(capturedDisableStates.animate);
+ assertEquals(capturedDisableStates.displaysWithStates.size(), 2);
+ Pair<Integer, Integer> display0States = capturedDisableStates.displaysWithStates.get(0);
+ assertEquals((int) display0States.first, expectedFlags);
+ assertEquals((int) display0States.second, 0);
+ Pair<Integer, Integer> display2States = capturedDisableStates.displaysWithStates.get(
+ SECONDARY_DISPLAY_ID);
+ assertEquals((int) display2States.first, expectedFlags);
+ assertEquals((int) display2States.second, 0);
+ }
+
+ @Test
public void testSetHomeDisabled() throws Exception {
int expectedFlags = DISABLE_MASK & DISABLE_HOME;
String pkg = mContext.getPackageName();
@@ -851,6 +891,40 @@ public class StatusBarManagerServiceTest {
}
@Test
+ @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS)
+ public void testDisable2ForAllDisplays() throws Exception {
+ int user1Id = 0;
+ mockUidCheck();
+ mockCurrentUserCheck(user1Id);
+
+ mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID);
+
+ int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE;
+ String pkg = mContext.getPackageName();
+
+ // before disabling
+ assertEquals(DISABLE_NONE,
+ mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]);
+
+ // disable
+ mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg);
+
+ ArgumentCaptor<DisableStates> disableStatesCaptor = ArgumentCaptor.forClass(
+ DisableStates.class);
+ verify(mMockStatusBar).disableForAllDisplays(disableStatesCaptor.capture());
+ DisableStates capturedDisableStates = disableStatesCaptor.getValue();
+ assertTrue(capturedDisableStates.animate);
+ assertEquals(capturedDisableStates.displaysWithStates.size(), 2);
+ Pair<Integer, Integer> display0States = capturedDisableStates.displaysWithStates.get(0);
+ assertEquals((int) display0States.first, 0);
+ assertEquals((int) display0States.second, expectedFlags);
+ Pair<Integer, Integer> display2States = capturedDisableStates.displaysWithStates.get(
+ SECONDARY_DISPLAY_ID);
+ assertEquals((int) display2States.first, 0);
+ assertEquals((int) display2States.second, expectedFlags);
+ }
+
+ @Test
public void testSetQuickSettingsDisabled2() throws Exception {
int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS;
String pkg = mContext.getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
index 4a97b4670289..291d0ec8fbfc 100644
--- a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java
@@ -85,7 +85,7 @@ public class DiskStatsLoggingServiceTest extends AndroidTestCase {
mDownloads = new TemporaryFolder();
mDownloads.create();
mStorageStats = new ExternalStorageStats();
- when(mSsm.queryExternalStatsForUser(isNull(String.class), any(UserHandle.class)))
+ when(mSsm.queryExternalStatsForUser((String)isNull(), any(UserHandle.class)))
.thenReturn(mStorageStats);
when(mJobService.getSystemService(anyString())).thenReturn(mSsm);
}
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index fbf906586e8b..c59f0a05c619 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -17,6 +17,7 @@
package com.android.server.supervision
import android.app.Activity
+import android.app.KeyguardManager
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManagerInternal
import android.app.supervision.flags.Flags
@@ -61,6 +62,9 @@ class SupervisionServiceTest {
@get:Rule val mocks: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var mockDpmInternal: DevicePolicyManagerInternal
+
+ @Mock
+ private lateinit var mockKeyguardManager: KeyguardManager
@Mock private lateinit var mockPackageManager: PackageManager
@Mock private lateinit var mockUserManagerInternal: UserManagerInternal
@@ -71,7 +75,7 @@ class SupervisionServiceTest {
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
- context = SupervisionContextWrapper(context, mockPackageManager)
+ context = SupervisionContextWrapper(context, mockKeyguardManager, mockPackageManager)
LocalServices.removeServiceForTest(DevicePolicyManagerInternal::class.java)
LocalServices.addService(DevicePolicyManagerInternal::class.java, mockDpmInternal)
@@ -250,11 +254,41 @@ class SupervisionServiceTest {
@Test
fun createConfirmSupervisionCredentialsIntent() {
+ service.mInternal.setSupervisionEnabledForUser(context.getUserId(), true)
+ whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(SUPERVISING_USER_ID)
+ whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(true)
+
val intent = checkNotNull(service.createConfirmSupervisionCredentialsIntent())
assertThat(intent.action).isEqualTo(ACTION_CONFIRM_SUPERVISION_CREDENTIALS)
assertThat(intent.getPackage()).isEqualTo("com.android.settings")
}
+ @Test
+ fun createConfirmSupervisionCredentialsIntent_supervisionNotEnabled_returnsNull() {
+ service.mInternal.setSupervisionEnabledForUser(context.getUserId(), false)
+ whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(SUPERVISING_USER_ID)
+ whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(true)
+
+ assertThat(service.createConfirmSupervisionCredentialsIntent()).isNull()
+ }
+
+ @Test
+ fun createConfirmSupervisionCredentialsIntent_noSupervisingUser_returnsNull() {
+ service.mInternal.setSupervisionEnabledForUser(context.getUserId(), true)
+ whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(UserHandle.USER_NULL)
+
+ assertThat(service.createConfirmSupervisionCredentialsIntent()).isNull()
+ }
+
+ @Test
+ fun createConfirmSupervisionCredentialsIntent_supervisingUserMissingSecureLock_returnsNull() {
+ service.mInternal.setSupervisionEnabledForUser(context.getUserId(), true)
+ whenever(mockUserManagerInternal.getSupervisingProfileId()).thenReturn(SUPERVISING_USER_ID)
+ whenever(mockKeyguardManager.isDeviceSecure(SUPERVISING_USER_ID)).thenReturn(false)
+
+ assertThat(service.createConfirmSupervisionCredentialsIntent()).isNull()
+ }
+
private val systemSupervisionPackage: String
get() = context.getResources().getString(R.string.config_systemSupervision)
@@ -279,6 +313,7 @@ class SupervisionServiceTest {
private companion object {
const val USER_ID = 100
const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+ const val SUPERVISING_USER_ID = 10
}
}
@@ -286,10 +321,19 @@ class SupervisionServiceTest {
* A context wrapper that allows broadcast intents to immediately invoke the receivers without
* performing checks on the sending user.
*/
-private class SupervisionContextWrapper(val context: Context, val pkgManager: PackageManager) :
- ContextWrapper(context) {
+private class SupervisionContextWrapper(
+ val context: Context,
+ val keyguardManager: KeyguardManager,
+ val pkgManager: PackageManager,
+) : ContextWrapper(context) {
val interceptors = mutableListOf<Pair<BroadcastReceiver, IntentFilter>>()
+ override fun getSystemService(name: String): Any =
+ when (name) {
+ Context.KEYGUARD_SERVICE -> keyguardManager
+ else -> super.getSystemService(name)
+ }
+
override fun getPackageManager() = pkgManager
override fun registerReceiverForAllUsers(
diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
index 857a9767d9ee..842c441e09f2 100644
--- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java
@@ -452,14 +452,14 @@ public class SystemConfigTest {
+ " <library \n"
+ " name=\"foo\"\n"
+ " file=\"" + mFooJar + "\"\n"
- + " on-bootclasspath-before=\"A\"\n"
+ + " on-bootclasspath-before=\"Q\"\n"
+ " on-bootclasspath-since=\"W\"\n"
+ " />\n\n"
+ " </permissions>";
parseSharedLibraries(contents);
assertFooIsOnlySharedLibrary();
SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo");
- assertThat(entry.onBootclasspathBefore).isEqualTo("A");
+ assertThat(entry.onBootclasspathBefore).isEqualTo("Q");
assertThat(entry.onBootclasspathSince).isEqualTo("W");
}
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp b/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp
new file mode 100644
index 000000000000..dcf9cc216687
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/Android.bp
@@ -0,0 +1,38 @@
+// 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+ name: "TopologyTestApp",
+
+ srcs: ["**/*.java"],
+
+ dex_preopt: {
+ enabled: false,
+ },
+ optimize: {
+ enabled: false,
+ },
+
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml
new file mode 100644
index 000000000000..dad2315148df
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.servicestests.apps.topologytestapp">
+
+ <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+
+ <application android:label="TopologyUpdateTestApp">
+ <activity android:name="com.android.servicestests.apps.topologytestapp.TopologyUpdateActivity"
+ android:exported="true" />
+ </application>
+
+</manifest>
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS b/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS
new file mode 100644
index 000000000000..e9557f84f8fb
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 345010
+
+include /services/core/java/com/android/server/display/OWNERS
diff --git a/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java b/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.java
new file mode 100644
index 000000000000..b35ba3c2c60c
--- /dev/null
+++ b/services/tests/servicestests/test-apps/TopologyTestApp/src/com/android/servicestests/apps/topologytestapp/TopologyUpdateActivity.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.servicestests.apps.topologytestapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayTopology;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.function.Consumer;
+
+/**
+ * A simple activity listening to topology updates
+ */
+public class TopologyUpdateActivity extends Activity {
+ public static final int MESSAGE_LAUNCHED = 1;
+ public static final int MESSAGE_CALLBACK = 2;
+
+ private static final String TAG = TopologyUpdateActivity.class.getSimpleName();
+
+ private static final String TEST_MESSENGER = "MESSENGER";
+
+ private Messenger mMessenger;
+ private DisplayManager mDisplayManager;
+ private final Consumer<DisplayTopology> mTopologyListener = this::callback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ mMessenger = intent.getParcelableExtra(TEST_MESSENGER, Messenger.class);
+ mDisplayManager = getApplicationContext().getSystemService(DisplayManager.class);
+ mDisplayManager.registerTopologyListener(getMainExecutor(), mTopologyListener);
+ launched();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDisplayManager.unregisterTopologyListener(mTopologyListener);
+ }
+
+ private void launched() {
+ try {
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_LAUNCHED;
+ msg.arg1 = android.os.Process.myPid();
+ msg.arg2 = Process.myUid();
+ Log.d(TAG, "Launched");
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void callback(DisplayTopology topology) {
+ try {
+ Message msg = Message.obtain();
+ msg.what = MESSAGE_CALLBACK;
+ msg.obj = topology;
+ Log.d(TAG, "Msg " + topology);
+ mMessenger.send(msg);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index fcdf88f16550..0495e967c0e3 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -39,8 +39,6 @@ import androidx.test.filters.MediumTest;
import com.android.hardware.input.Flags;
import com.android.internal.annotations.Keep;
-import junit.framework.Assert;
-
import junitparams.JUnitParamsRunner;
import junitparams.Parameters;
@@ -433,112 +431,94 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
@Test
public void testKeyGestureRecentApps() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
mPhoneWindowManager.assertShowRecentApps();
}
@Test
public void testKeyGestureAppSwitch() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
mPhoneWindowManager.assertToggleRecentApps();
}
@Test
public void testKeyGestureLaunchAssistant() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
mPhoneWindowManager.assertSearchManagerLaunchAssist();
}
@Test
public void testKeyGestureLaunchVoiceAssistant() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT);
mPhoneWindowManager.assertSearchManagerLaunchAssist();
}
@Test
public void testKeyGestureGoHome() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
mPhoneWindowManager.assertGoToHomescreen();
}
@Test
public void testKeyGestureLaunchSystemSettings() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS);
mPhoneWindowManager.assertLaunchSystemSettings();
}
@Test
public void testKeyGestureLock() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN);
mPhoneWindowManager.assertLockedAfterAppTransitionFinished();
}
@Test
public void testKeyGestureToggleNotificationPanel() throws RemoteException {
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
mPhoneWindowManager.assertTogglePanel();
}
@Test
public void testKeyGestureScreenshot() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT);
mPhoneWindowManager.assertTakeScreenshotCalled();
}
@Test
public void testKeyGestureTriggerBugReport() throws RemoteException {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT);
mPhoneWindowManager.assertTakeBugreport(true);
}
@Test
public void testKeyGestureBack() {
- Assert.assertTrue(sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
mPhoneWindowManager.assertBackEventInjected();
}
@Test
public void testKeyGestureMultiWindowNavigation() {
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION);
mPhoneWindowManager.assertMoveFocusedTaskToFullscreen();
}
@Test
public void testKeyGestureDesktopMode() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE);
mPhoneWindowManager.assertMoveFocusedTaskToDesktop();
}
@Test
public void testKeyGestureSplitscreenNavigation() {
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT);
mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(true);
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT);
mPhoneWindowManager.assertMoveFocusedTaskToStageSplit(false);
}
@Test
public void testKeyGestureShortcutHelper() {
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER);
mPhoneWindowManager.assertToggleShortcutsMenu();
}
@@ -549,173 +529,139 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
for (int i = 0; i < currentBrightness.length; i++) {
mPhoneWindowManager.prepareBrightnessDecrease(currentBrightness[i]);
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN);
mPhoneWindowManager.verifyNewBrightness(newBrightness[i]);
}
}
@Test
public void testKeyGestureRecentAppSwitcher() {
- Assert.assertTrue(sendKeyGestureEventStart(
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER);
mPhoneWindowManager.assertShowRecentApps();
-
- Assert.assertTrue(sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER);
mPhoneWindowManager.assertHideRecentApps();
}
@Test
public void testKeyGestureLanguageSwitch() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH);
mPhoneWindowManager.assertSwitchKeyboardLayout(1, DEFAULT_DISPLAY);
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
- KeyEvent.META_SHIFT_ON));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ KeyEvent.META_SHIFT_ON);
mPhoneWindowManager.assertSwitchKeyboardLayout(-1, DEFAULT_DISPLAY);
}
@Test
public void testKeyGestureLaunchSearch() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH);
mPhoneWindowManager.assertLaunchSearch();
}
@Test
public void testKeyGestureScreenshotChord() {
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
mPhoneWindowManager.moveTimeForward(500);
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
mPhoneWindowManager.assertTakeScreenshotCalled();
}
@Test
public void testKeyGestureScreenshotChordCancelled() {
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_SCREENSHOT_CHORD);
mPhoneWindowManager.assertTakeScreenshotNotCalled();
}
@Test
public void testKeyGestureRingerToggleChord() {
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD);
mPhoneWindowManager.moveTimeForward(500);
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD);
mPhoneWindowManager.assertVolumeMute();
}
@Test
public void testKeyGestureRingerToggleChordCancelled() {
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_MUTE);
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD);
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_RINGER_TOGGLE_CHORD);
mPhoneWindowManager.assertVolumeNotMuted();
}
@Test
public void testKeyGestureGlobalAction() {
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS);
mPhoneWindowManager.moveTimeForward(500);
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS);
mPhoneWindowManager.assertShowGlobalActionsCalled();
}
@Test
public void testKeyGestureGlobalActionCancelled() {
mPhoneWindowManager.overridePowerVolumeUp(POWER_VOLUME_UP_BEHAVIOR_GLOBAL_ACTIONS);
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS);
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_GLOBAL_ACTIONS);
mPhoneWindowManager.assertShowGlobalActionsNotCalled();
}
@Test
public void testKeyGestureTvTriggerBugReport() {
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
mPhoneWindowManager.moveTimeForward(1000);
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
mPhoneWindowManager.assertBugReportTakenForTv();
}
@Test
public void testKeyGestureTvTriggerBugReportCancelled() {
- Assert.assertTrue(
- sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
- Assert.assertTrue(
- sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT));
+ sendKeyGestureEventStart(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
+ sendKeyGestureEventCancel(KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT);
mPhoneWindowManager.assertBugReportNotTakenForTv();
}
@Test
public void testKeyGestureAccessibilityShortcut() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT);
mPhoneWindowManager.assertAccessibilityKeychordCalled();
}
@Test
public void testKeyGestureCloseAllDialogs() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS);
mPhoneWindowManager.assertCloseAllDialogs();
}
@Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES)
public void testKeyGestureToggleTalkback() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK);
mPhoneWindowManager.assertTalkBack(true);
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK);
mPhoneWindowManager.assertTalkBack(false);
}
@Test
@EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES)
public void testKeyGestureToggleVoiceAccess() {
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS);
mPhoneWindowManager.assertVoiceAccess(true);
- Assert.assertTrue(
- sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS);
mPhoneWindowManager.assertVoiceAccess(false);
}
@Test
public void testKeyGestureToggleDoNotDisturb() {
mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_OFF);
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB);
mPhoneWindowManager.assertZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
- Assert.assertTrue(
- sendKeyGestureEventComplete(
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB));
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_DO_NOT_DISTURB);
mPhoneWindowManager.assertZenMode(Settings.Global.ZEN_MODE_OFF);
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 32a3b7f2c9cc..8d164e1acf74 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -35,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.policy.PhoneWindowManager.EXTRA_TRIGGER_HUB;
+import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_HUB_OR_DREAM_OR_SLEEP;
import static com.google.common.truth.Truth.assertThat;
@@ -43,6 +44,7 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -51,18 +53,21 @@ import android.app.AppOpsManager;
import android.content.Context;
import android.hardware.input.InputManager;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.testing.TestableContext;
-import android.view.contentprotection.flags.Flags;
+import android.view.KeyEvent;
import androidx.test.filters.SmallTest;
import com.android.internal.util.test.LocalServiceKeeperRule;
+import com.android.internal.widget.LockPatternUtils;
import com.android.server.input.InputManagerInternal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
@@ -99,6 +104,8 @@ public class PhoneWindowManagerTests {
public final TestableContext mContext = spy(
new TestableContext(getInstrumentation().getContext()));
+ @Mock private IBinder mInputToken;
+
PhoneWindowManager mPhoneWindowManager;
@Mock
private ActivityTaskManagerInternal mAtmInternal;
@@ -119,6 +126,10 @@ public class PhoneWindowManagerTests {
private DisplayPolicy mDisplayPolicy;
@Mock
private KeyguardServiceDelegate mKeyguardServiceDelegate;
+ @Mock
+ private LockPatternUtils mLockPatternUtils;
+
+ private static final int INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY = 0;
@Before
public void setUp() {
@@ -146,7 +157,7 @@ public class PhoneWindowManagerTests {
mPhoneWindowManager.mKeyguardDelegate = mKeyguardServiceDelegate;
final InputManager im = mock(InputManager.class);
- doNothing().when(im).registerKeyGestureEventHandler(any());
+ doNothing().when(im).registerKeyGestureEventHandler(anyList(), any());
doReturn(im).when(mContext).getSystemService(eq(Context.INPUT_SERVICE));
}
@@ -211,7 +222,7 @@ public class PhoneWindowManagerTests {
@Test
public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
- mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -220,7 +231,7 @@ public class PhoneWindowManagerTests {
@Test
public void testCheckAddPermission_withAccessibilityOverlay() {
- mSetFlagsRule.enableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -229,7 +240,7 @@ public class PhoneWindowManagerTests {
@Test
public void testCheckAddPermission_withAccessibilityOverlay_flagDisabled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+ mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
int[] outAppOp = new int[1];
assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
/* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp, DEFAULT_DISPLAY));
@@ -253,6 +264,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_goesToSleepFromDream() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -274,6 +286,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_hubAvailableLocks() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
mContext.getTestablePermissions().setPermission(android.Manifest.permission.DEVICE_POWER,
PERMISSION_GRANTED);
initPhoneWindowManager();
@@ -302,6 +315,7 @@ public class PhoneWindowManagerTests {
@Test
public void powerPress_hubOrDreamOrSleep_hubNotAvailableDreams() {
when(mDisplayPolicy.isAwake()).thenReturn(true);
+ when(mLockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false);
initPhoneWindowManager();
// Set power button behavior.
@@ -322,6 +336,106 @@ public class PhoneWindowManagerTests {
verify(mDreamManagerInternal).requestDream();
}
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_awakeFromDream() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream when device is dreaming.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is stopped.
+ verify(mDreamManagerInternal)
+ .stopDream(false /*immediate*/, "short press power" /*reason*/);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_canNotDreamGoToSleep() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can not dream for other reasons.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(false);
+ // Device is not dreaming.
+ when(mDreamManagerInternal.isDreaming()).thenReturn(false);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Device goes to sleep.
+ verify(mPowerManager).goToSleep(eventTime, PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
+ }
+
+ @Test
+ public void powerPress_dreamOrAwakeOrSleep_dreamFromActive() {
+ when(mDisplayPolicy.isAwake()).thenReturn(true);
+ initPhoneWindowManager();
+
+ // Set power button behavior.
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.POWER_BUTTON_SHORT_PRESS,
+ SHORT_PRESS_POWER_DREAM_OR_AWAKE_OR_SLEEP);
+ mPhoneWindowManager.updateSettings(null);
+
+ // Can dream when active.
+ when(mDreamManagerInternal.canStartDreaming(any(Boolean.class))).thenReturn(true);
+
+ // Power button pressed.
+ int eventTime = 0;
+ mPhoneWindowManager.powerPress(eventTime, 1, 0);
+
+ // Dream is requested.
+ verify(mDreamManagerInternal).requestDream();
+ }
+
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_FIX_SEARCH_MODIFIER_FALLBACKS)
+ public void testInterceptKeyBeforeDispatching() {
+ // Handle sub-tasks of init().
+ doNothing().when(mPhoneWindowManager).updateSettings(any());
+ doNothing().when(mPhoneWindowManager).initializeHdmiState();
+ final DisplayPolicy displayPolicy = mock(DisplayPolicy.class);
+ mPhoneWindowManager.mDefaultDisplayPolicy = displayPolicy;
+ mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
+ final PowerManager pm = mock(PowerManager.class);
+ doReturn(true).when(pm).isInteractive();
+ doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));
+
+ mContext.getMainThreadHandler().runWithScissors(() -> mPhoneWindowManager.init(
+ new PhoneWindowManager.Injector(mContext,
+ mock(WindowManagerPolicy.WindowManagerFuncs.class))), 0);
+
+ // Case: KeyNotConsumed with meta key.
+ KeyEvent keyEvent = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_A, 0, KeyEvent.META_META_ON);
+ long result = mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, keyEvent, 0);
+ assertEquals(INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY, result);
+
+ // Case: KeyNotConsumed without meta key.
+ KeyEvent keyEvent1 = new KeyEvent(0, 0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_ESCAPE, 0, 0);
+ long result1 = mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, keyEvent1, 0);
+ assertEquals(INTERCEPT_SYSTEM_KEY_NOT_CONSUMED_DELAY, result1);
+ }
+
private void initPhoneWindowManager() {
mPhoneWindowManager.mDefaultDisplayPolicy = mDisplayPolicy;
mPhoneWindowManager.mDefaultDisplayRotation = mock(DisplayRotation.class);
@@ -345,6 +459,11 @@ public class PhoneWindowManagerTests {
return mKeyguardServiceDelegate;
}
+ @Override
+ LockPatternUtils getLockPatternUtils() {
+ return mLockPatternUtils;
+ }
+
/**
* {@code WindowWakeUpPolicy} registers a local service in its constructor, easier to just
* mock it out so we don't have to unregister it after every test.
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index c57adfd69b06..f89c6f638384 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -238,33 +238,33 @@ class ShortcutKeyTestBase {
sendKeyCombination(new int[]{keyCode}, durationMillis, false, DEFAULT_DISPLAY);
}
- boolean sendKeyGestureEventStart(int gestureType) {
- return mPhoneWindowManager.sendKeyGestureEvent(
+ void sendKeyGestureEventStart(int gestureType) {
+ mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
KeyGestureEvent.ACTION_GESTURE_START).build());
}
- boolean sendKeyGestureEventComplete(int gestureType) {
- return mPhoneWindowManager.sendKeyGestureEvent(
+ void sendKeyGestureEventComplete(int gestureType) {
+ mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
}
- boolean sendKeyGestureEventCancel(int gestureType) {
- return mPhoneWindowManager.sendKeyGestureEvent(
+ void sendKeyGestureEventCancel(int gestureType) {
+ mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setKeyGestureType(gestureType).setAction(
KeyGestureEvent.ACTION_GESTURE_COMPLETE).setFlags(
KeyGestureEvent.FLAG_CANCELLED).build());
}
- boolean sendKeyGestureEventComplete(int gestureType, int modifierState) {
- return mPhoneWindowManager.sendKeyGestureEvent(
+ void sendKeyGestureEventComplete(int gestureType, int modifierState) {
+ mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setModifierState(modifierState).setKeyGestureType(
gestureType).setAction(KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
}
- boolean sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) {
- return mPhoneWindowManager.sendKeyGestureEvent(
+ void sendKeyGestureEventComplete(int keycode, int modifierState, int gestureType) {
+ mPhoneWindowManager.sendKeyGestureEvent(
new KeyGestureEvent.Builder().setKeycodes(new int[]{keycode}).setModifierState(
modifierState).setKeyGestureType(gestureType).setAction(
KeyGestureEvent.ACTION_GESTURE_COMPLETE).build());
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index e56fd3c6272d..7b6d361c55d4 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -49,6 +49,7 @@ import static com.android.server.policy.PhoneWindowManager.POWER_VOLUME_UP_BEHAV
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.CALLS_REAL_METHODS;
import static org.mockito.Mockito.after;
@@ -353,7 +354,7 @@ class TestPhoneWindowManager {
doReturn(mAppOpsManager).when(mContext).getSystemService(eq(AppOpsManager.class));
doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class));
doReturn(mInputManager).when(mContext).getSystemService(eq(InputManager.class));
- doNothing().when(mInputManager).registerKeyGestureEventHandler(any());
+ doNothing().when(mInputManager).registerKeyGestureEventHandler(anyList(), any());
doNothing().when(mInputManager).unregisterKeyGestureEventHandler(any());
doReturn(mPackageManager).when(mContext).getPackageManager();
doReturn(mSensorPrivacyManager).when(mContext).getSystemService(
@@ -476,8 +477,8 @@ class TestPhoneWindowManager {
mPhoneWindowManager.interceptUnhandledKey(event, mInputToken);
}
- boolean sendKeyGestureEvent(KeyGestureEvent event) {
- return mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken);
+ void sendKeyGestureEvent(KeyGestureEvent event) {
+ mPhoneWindowManager.handleKeyGestureEvent(event, mInputToken);
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/DeviceStateAutoRotateSettingIssueLoggerTests.java b/services/tests/wmtests/src/com/android/server/wm/DeviceStateAutoRotateSettingIssueLoggerTests.java
index f76a9cdbb894..ba9bf1bf8045 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DeviceStateAutoRotateSettingIssueLoggerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DeviceStateAutoRotateSettingIssueLoggerTests.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import android.platform.test.annotations.Presubmit;
@@ -143,4 +144,46 @@ public class DeviceStateAutoRotateSettingIssueLoggerTests {
anyInt(),
anyBoolean()), never());
}
+
+ @Test
+ public void onStateChange_issueOccurredSettingChangedTwice_reportOnlyOnce() {
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateAutoRotateSettingChange();
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateChange();
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateAutoRotateSettingChange();
+
+ verify(() ->
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_REPORTED),
+ anyInt(),
+ anyBoolean()), times(1));
+ }
+
+ @Test
+ public void onStateChange_issueOccurredDeviceStateChangedTwice_reportOnlyOnce() {
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateChange();
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateAutoRotateSettingChange();
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateChange();
+
+ verify(() ->
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_REPORTED),
+ anyInt(),
+ anyBoolean()), times(1));
+ }
+
+ @Test
+ public void onStateChange_issueOccurredAfterDelay_reportOnce() {
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateAutoRotateSettingChange();
+ mTestTimeSupplier.delay(
+ DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_THRESHOLD_MILLIS + DELAY);
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateChange();
+ mTestTimeSupplier.delay(DELAY);
+ mDeviceStateAutoRotateSettingIssueLogger.onDeviceStateAutoRotateSettingChange();
+
+ verify(() ->
+ FrameworkStatsLog.write(
+ eq(FrameworkStatsLog.DEVICE_STATE_AUTO_ROTATE_SETTING_ISSUE_REPORTED),
+ eq(DELAY),
+ anyBoolean()), times(1));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 044aacc4b988..b617f0285606 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -100,8 +100,6 @@ import androidx.test.filters.MediumTest;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import libcore.junit.util.compat.CoreCompatChangeRule;
-
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
@@ -414,79 +412,96 @@ public class TaskTests extends WindowTestsBase {
}
@Test
- @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
- public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_Resizeable() {
+ public void testIsResizeable_nonResizeable_forceResize_overridesEnabled_resizeable() {
final Task task = new TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setComponent(
- ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
.build();
task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+ final ActivityRecord activity = task.getRootActivity();
+ final AppCompatResizeOverrides resizeOverrides =
+ activity.mAppCompatController.getResizeOverrides();
+ spyOn(activity);
+ spyOn(resizeOverrides);
+ doReturn(true).when(resizeOverrides).shouldOverrideForceResizeApp();
+ task.intent = null;
+ task.setIntent(activity);
// Override should take effect and task should be resizeable.
assertTrue(task.getTaskInfo().isResizeable);
}
@Test
- @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
- public void testIsResizeable_nonResizeable_forceResize_overridesDisabled_nonResizeable() {
+ public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() {
final Task task = new TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setComponent(
- ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
.build();
- task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
-
- // Disallow resize overrides.
- task.mAllowForceResizeOverride = false;
+ task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+ final ActivityRecord activity = task.getRootActivity();
+ final AppCompatResizeOverrides resizeOverrides =
+ activity.mAppCompatController.getResizeOverrides();
+ spyOn(activity);
+ spyOn(resizeOverrides);
+ doReturn(true).when(resizeOverrides).shouldOverrideForceNonResizeApp();
+ task.intent = null;
+ task.setIntent(activity);
- // Override should not take effect and task should be un-resizeable.
+ // Override should take effect and task should be un-resizeable.
assertFalse(task.getTaskInfo().isResizeable);
}
@Test
- @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
- public void testIsResizeable_resizeable_forceNonResize_overridesEnabled_nonResizeable() {
+ public void testIsResizeable_resizeableTask_fullscreenOverride_resizeable() {
final Task task = new TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setComponent(
- ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
.build();
- task.setResizeMode(RESIZE_MODE_RESIZEABLE);
+ task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+ final ActivityRecord activity = task.getRootActivity();
+ final AppCompatAspectRatioOverrides aspectRatioOverrides =
+ activity.mAppCompatController.getAspectRatioOverrides();
+ spyOn(aspectRatioOverrides);
+ doReturn(true).when(aspectRatioOverrides).hasFullscreenOverride();
+ task.intent = null;
+ task.setIntent(activity);
- // Override should take effect and task should be un-resizeable.
- assertFalse(task.getTaskInfo().isResizeable);
+ // Override should take effect and task should be resizeable.
+ assertTrue(task.getTaskInfo().isResizeable);
}
@Test
- @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
- public void testIsResizeable_resizeable_forceNonResize_overridesDisabled_Resizeable() {
+ public void testIsResizeable_resizeableTask_universalResizeable_resizeable() {
final Task task = new TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setComponent(
- ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
.build();
- task.setResizeMode(RESIZE_MODE_RESIZEABLE);
-
- // Disallow resize overrides.
- task.mAllowForceResizeOverride = false;
+ task.setResizeMode(RESIZE_MODE_UNRESIZEABLE);
+ final ActivityRecord activity = task.getRootActivity();
+ spyOn(activity);
+ doReturn(true).when(activity).isUniversalResizeable();
+ task.intent = null;
+ task.setIntent(activity);
- // Override should not take effect and task should be resizeable.
+ // Override should take effect and task should be resizeable.
assertTrue(task.getTaskInfo().isResizeable);
}
@Test
- @CoreCompatChangeRule.EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
- public void testIsResizeable_systemWideForceResize_compatForceNonResize__Resizeable() {
+ public void testIsResizeable_systemWideForceResize_compatForceNonResize_resizeable() {
final Task task = new TaskBuilder(mSupervisor)
.setCreateActivity(true)
- .setComponent(
- ComponentName.createRelative(mContext, SizeCompatTests.class.getName()))
+ .setComponent(ComponentName.createRelative(mContext, TaskTests.class.getName()))
.build();
task.setResizeMode(RESIZE_MODE_RESIZEABLE);
// Set system-wide force resizeable override.
task.mAtmService.mForceResizableActivities = true;
+ final ActivityRecord activity = task.getRootActivity();
+ final AppCompatResizeOverrides resizeOverrides =
+ activity.mAppCompatController.getResizeOverrides();
+ spyOn(activity);
+ spyOn(resizeOverrides);
+ doReturn(true).when(resizeOverrides).shouldOverrideForceNonResizeApp();
+ task.intent = null;
+ task.setIntent(activity);
+
// System wide override should tak priority over app compat override so the task should
// remain resizeable.
assertTrue(task.getTaskInfo().isResizeable);
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index c244168c65fd..51ce144881b7 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2912,38 +2912,48 @@ public final class Call {
if (bundle.size() != newBundle.size()) {
return false;
}
-
- for(String key : bundle.keySet()) {
- if (key != null) {
- if (!newBundle.containsKey(key)) {
- return false;
- }
- // In case new call extra contains non-framework class objects, return false to
- // force update the call extra
- try {
- final Object value = bundle.get(key);
- final Object newValue = newBundle.get(key);
- if (value instanceof Bundle && newValue instanceof Bundle) {
- if (!areBundlesEqual((Bundle) value, (Bundle) newValue)) {
- return false;
- }
+ try {
+ for (String key : bundle.keySet()) {
+ if (key != null) {
+ if (!newBundle.containsKey(key)) {
+ return false;
}
- if (value instanceof byte[] && newValue instanceof byte[]) {
- if (!Arrays.equals((byte[]) value, (byte[]) newValue)) {
+ // In case new call extra contains non-framework class objects, return false to
+ // force update the call extra
+ try {
+ final Object value = bundle.get(key);
+ final Object newValue = newBundle.get(key);
+ if (value instanceof Bundle && newValue instanceof Bundle) {
+ if (!areBundlesEqual((Bundle) value, (Bundle) newValue)) {
+ return false;
+ }
+ }
+ if (value instanceof byte[] && newValue instanceof byte[]) {
+ if (!Arrays.equals((byte[]) value, (byte[]) newValue)) {
+ return false;
+ }
+ } else if (!Objects.equals(value, newValue)) {
return false;
}
- } else if (!Objects.equals(value, newValue)) {
+ } catch (BadParcelableException e) {
return false;
}
- } catch (BadParcelableException e) {
- return false;
- } catch (ClassCastException e) {
- Log.e(LOG_TAG, e, "areBundlesEqual: failure comparing bundle key %s", key);
- // until we know what is causing this, we should rethrow -- this is still not
- // expected.
- throw e;
}
}
+ } catch (ClassCastException | ArrayIndexOutOfBoundsException e) {
+ // Unfortunately this may get raised when accessing the bundle's keyset, so we cannot
+ // determine WHY a class cast exception is happening. We had tried in the past to do
+ // this down in the for loop so we could figure out which key is causing an issue.
+ // Bundles are not thread safe, so the most likely issue here is that the InCallService
+ // implementation is accessing the Bundle WHILE an incoming Telecom update comes in to
+ // potentially replace the Bundle. We call "areBundlesEqual" to see if the newly
+ // unparceled Call.Details is the same as what is already in the current Call instance.
+ // If those two operations overleave, I can see the potential for concurrent
+ // modification and edit of the Bundle. So we'll just catch here and assume the Bundles
+ // are not the same. This means a Call.CallBack may fire the onCallDetails changed
+ // callback when the Bundle didn't actually change.
+ Log.e(LOG_TAG, e, "areBundlesEqual: failed!");
+ return false;
}
return true;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 14915109999c..19574fd95ac7 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -11493,17 +11493,17 @@ public class CarrierConfigManager {
+ "target=GERAN|UTRAN|EUTRAN|NGRAN|IWLAN, type=allowed"});
PersistableBundle auto_data_switch_rat_signal_score_string_bundle = new PersistableBundle();
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_SA_MMWAVE", new int[]{10000, 13227, 16000, 18488, 20017});
+ "NR_SA_MMWAVE", new int[]{6300, 10227, 16000, 18488, 19017});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_NSA_MMWAVE", new int[]{8000, 10227, 12488, 15017, 15278});
+ "NR_NSA_MMWAVE", new int[]{5700, 9227, 12488, 13517, 15978});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"LTE", new int[]{3731, 5965, 8618, 11179, 13384});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "LTE_CA", new int[]{3831, 6065, 8718, 11379, 13484});
+ "LTE_CA", new int[]{3831, 6065, 8718, 11379, 14484});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_SA", new int[]{5288, 6795, 6955, 7562, 9713});
+ "NR_SA", new int[]{2288, 6795, 6955, 7562, 15484});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
- "NR_NSA", new int[]{5463, 6827, 8029, 9007, 9428});
+ "NR_NSA", new int[]{2463, 6827, 8029, 9007, 15884});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
"UMTS", new int[]{100, 169, 183, 192, 300});
auto_data_switch_rat_signal_score_string_bundle.putIntArray(
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 270d599e0a0a..6b4b0ee93684 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -700,6 +700,56 @@ public final class SatelliteManager {
public @interface DisplayMode {}
/**
+ * Unknown or unsupported value for data mode on satellite.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final int SATELLITE_DATA_SUPPORT_UNKNOWN = -1;
+
+ /**
+ * Support only restricted data usecases like carrier messaging using RCS.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final int SATELLITE_DATA_SUPPORT_RESTRICTED = 0;
+
+ /**
+ * Support constrained internet which would enable internet only for applications that are
+ * modified.
+ *
+ * <p>
+ * To get internet access, applications need to be modified to use the satellite data
+ * optimized network. This can be done by setting the {@link #PROPERTY_SATELLITE_DATA_OPTIMIZED}
+ * property to {@code true} in the manifest.
+ * </p>
+ *
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final int SATELLITE_DATA_SUPPORT_CONSTRAINED = 1;
+
+ /**
+ * Support default internet on satellite without any restrictions on any apps.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ public static final int SATELLITE_DATA_SUPPORT_UNCONSTRAINED = 2;
+
+ /** @hide */
+ @IntDef(prefix = {"SATELLITE_DATA_SUPPORT_"}, value = {
+ SATELLITE_DATA_SUPPORT_UNKNOWN,
+ SATELLITE_DATA_SUPPORT_RESTRICTED,
+ SATELLITE_DATA_SUPPORT_CONSTRAINED,
+ SATELLITE_DATA_SUPPORT_UNCONSTRAINED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteDataSupportMode {}
+
+ /**
* The emergency call is handed over to oem-enabled satellite SOS messaging. SOS messages are
* sent to SOS providers, which will then forward the messages to emergency providers.
* @hide
@@ -3788,6 +3838,39 @@ public final class SatelliteManager {
return appsNames;
}
+ /**
+ * Method to return the current satellite data service policy supported mode for the
+ * subscriptionId based on carrier config.
+ *
+ * @param subId current subscription id.
+ *
+ * @return Supported modes {@link SatelliteDataSupportMode}
+ * @throws IllegalArgumentException if the subscription is invalid.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_SATELLITE_25Q4_APIS)
+ @SatelliteDataSupportMode
+ public int getSatelliteDataSupportMode(int subId) {
+ int satelliteMode = SATELLITE_DATA_SUPPORT_UNKNOWN;
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ satelliteMode = telephony.getSatelliteDataSupportMode(subId);
+ } else {
+ throw new IllegalStateException("telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getSatelliteDataSupportMode() RemoteException:" + ex);
+ ex.rethrowAsRuntimeException();
+ }
+
+ return satelliteMode;
+ }
+
@Nullable
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index b8aa9e8646bd..d7f80a94081a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3641,4 +3641,19 @@ interface ITelephony {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ "android.Manifest.permission.SATELLITE_COMMUNICATION)")
List<String> getSatelliteDataOptimizedApps();
+
+ /**
+ * Method to return the current satellite data service policy supported mode for the
+ * subscriptionId based on subscription id. Note: Iif any error or invalid sub id
+ * {@Link SatelliteDataSupportMode#SATELLITE_DATA_SUPPORT_UNKNOWN} will be returned.
+ *
+ * @param subId current subscription id.
+ *
+ * @return Supported modes {@link SatelliteDataSupportMode}
+ * @throws IllegalArgumentException if the subscription is invalid.
+ * @hide
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int getSatelliteDataSupportMode(in int subId);
}
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
index 794fd0255726..c62bd0b72584 100644
--- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt
@@ -18,12 +18,10 @@ package android.hardware.input
import android.content.Context
import android.content.ContextWrapper
-import android.os.IBinder
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
import android.view.KeyEvent
import androidx.test.core.app.ApplicationProvider
-import com.android.server.testutils.any
import com.android.test.input.MockInputManagerRule
import org.junit.Before
import org.junit.Rule
@@ -37,6 +35,7 @@ import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.fail
+import org.junit.Assert.assertThrows
/**
* Tests for [InputManager.KeyGestureEventHandler].
@@ -82,7 +81,7 @@ class KeyGestureEventHandlerTest {
// Handle key gesture handler registration.
doAnswer {
- val listener = it.getArgument(0) as IKeyGestureHandler
+ val listener = it.getArgument(1) as IKeyGestureHandler
if (registeredListener != null &&
registeredListener!!.asBinder() != listener.asBinder()) {
// There can only be one registered key gesture handler per process.
@@ -90,7 +89,7 @@ class KeyGestureEventHandlerTest {
}
registeredListener = listener
null
- }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any())
+ }.`when`(inputManagerRule.mock).registerKeyGestureHandler(Mockito.any(), Mockito.any())
// Handle key gesture handler being unregistered.
doAnswer {
@@ -101,7 +100,7 @@ class KeyGestureEventHandlerTest {
}
registeredListener = null
null
- }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any())
+ }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(Mockito.any())
}
private fun handleKeyGestureEvent(event: KeyGestureEvent) {
@@ -121,11 +120,12 @@ class KeyGestureEventHandlerTest {
var callbackCount = 0
// Add a key gesture event listener
- inputManager.registerKeyGestureEventHandler(KeyGestureHandler { event, _ ->
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME)
+ ) { event, _ ->
assertEquals(HOME_GESTURE_EVENT, event)
callbackCount++
- true
- })
+ }
// Request handling for key gesture event will notify the handler.
handleKeyGestureEvent(HOME_GESTURE_EVENT)
@@ -135,29 +135,41 @@ class KeyGestureEventHandlerTest {
@Test
fun testAddingHandlersRegistersInternalCallbackHandler() {
// Set up two callbacks.
- val callback1 = KeyGestureHandler { _, _ -> false }
- val callback2 = KeyGestureHandler { _, _ -> false }
+ val callback1 = InputManager.KeyGestureEventHandler { _, _ -> }
+ val callback2 = InputManager.KeyGestureEventHandler { _, _ -> }
assertNull(registeredListener)
// Adding the handler should register the callback with InputManagerService.
- inputManager.registerKeyGestureEventHandler(callback1)
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ callback1
+ )
assertNotNull(registeredListener)
// Adding another handler should not register new internal listener.
val currListener = registeredListener
- inputManager.registerKeyGestureEventHandler(callback2)
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ callback2
+ )
assertEquals(currListener, registeredListener)
}
@Test
fun testRemovingHandlersUnregistersInternalCallbackHandler() {
// Set up two callbacks.
- val callback1 = KeyGestureHandler { _, _ -> false }
- val callback2 = KeyGestureHandler { _, _ -> false }
+ val callback1 = InputManager.KeyGestureEventHandler { _, _ -> }
+ val callback2 = InputManager.KeyGestureEventHandler { _, _ -> }
- inputManager.registerKeyGestureEventHandler(callback1)
- inputManager.registerKeyGestureEventHandler(callback2)
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ callback1
+ )
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ callback2
+ )
// Only removing all handlers should remove the internal callback
inputManager.unregisterKeyGestureEventHandler(callback1)
@@ -172,47 +184,74 @@ class KeyGestureEventHandlerTest {
var callbackCount1 = 0
var callbackCount2 = 0
// Handler 1 captures all home gestures
- val callback1 = KeyGestureHandler { event, _ ->
+ val callback1 = InputManager.KeyGestureEventHandler { event, _ ->
callbackCount1++
- event.keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ assertEquals(KeyGestureEvent.KEY_GESTURE_TYPE_HOME, event.keyGestureType)
}
- // Handler 2 captures all gestures
- val callback2 = KeyGestureHandler { _, _ ->
+ // Handler 2 captures all back gestures
+ val callback2 = InputManager.KeyGestureEventHandler { event, _ ->
callbackCount2++
- true
+ assertEquals(KeyGestureEvent.KEY_GESTURE_TYPE_BACK, event.keyGestureType)
}
// Add both key gesture event handlers
- inputManager.registerKeyGestureEventHandler(callback1)
- inputManager.registerKeyGestureEventHandler(callback2)
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ callback1
+ )
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ callback2
+ )
- // Request handling for key gesture event, should notify callbacks in order. So, only the
- // first handler should receive a callback since it captures the event.
+ // Request handling for home key gesture event, should notify only callback1
handleKeyGestureEvent(HOME_GESTURE_EVENT)
assertEquals(1, callbackCount1)
assertEquals(0, callbackCount2)
- // Second handler should receive the event since the first handler doesn't capture the event
+ // Request handling for back key gesture event, should notify only callback2
handleKeyGestureEvent(BACK_GESTURE_EVENT)
- assertEquals(2, callbackCount1)
+ assertEquals(1, callbackCount1)
assertEquals(1, callbackCount2)
inputManager.unregisterKeyGestureEventHandler(callback1)
- // Request handling for key gesture event, should still trigger callback2 but not callback1.
+
+ // Request handling for home key gesture event, should not trigger callback2
handleKeyGestureEvent(HOME_GESTURE_EVENT)
- assertEquals(2, callbackCount1)
- assertEquals(2, callbackCount2)
+ assertEquals(1, callbackCount1)
+ assertEquals(1, callbackCount2)
+ }
+
+ @Test
+ fun testUnableToRegisterSameHandlerTwice() {
+ val handler = InputManager.KeyGestureEventHandler { _, _ -> }
+
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ handler
+ )
+
+ assertThrows(IllegalArgumentException::class.java) {
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK), handler
+ )
+ }
}
- inner class KeyGestureHandler(
- private var handler: (event: KeyGestureEvent, token: IBinder?) -> Boolean
- ) : InputManager.KeyGestureEventHandler {
+ @Test
+ fun testUnableToRegisterSameGestureTwice() {
+ val handler1 = InputManager.KeyGestureEventHandler { _, _ -> }
+ val handler2 = InputManager.KeyGestureEventHandler { _, _ -> }
+
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ handler1
+ )
- override fun handleKeyGestureEvent(
- event: KeyGestureEvent,
- focusedToken: IBinder?
- ): Boolean {
- return handler(event, focusedToken)
+ assertThrows(IllegalArgumentException::class.java) {
+ inputManager.registerKeyGestureEventHandler(
+ listOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME), handler2
+ )
}
}
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 7ec8f9ce9864..71c7a6b1119d 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -35,8 +35,8 @@ import android.os.PermissionEnforcer
import android.os.SystemClock
import android.os.test.FakePermissionEnforcer
import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.EnableFlags
+import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
import android.provider.Settings
import android.view.View.OnKeyListener
@@ -373,6 +373,29 @@ class InputManagerServiceTests {
}
@Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_FIX_SEARCH_MODIFIER_FALLBACKS)
+ fun testInterceptKeyBeforeDispatchingWithFallthroughEvent() {
+ service.systemRunning()
+ overrideSendActionKeyEventsToFocusedWindow(
+ /* hasPermission = */false,
+ /* hasPrivateFlag = */false
+ )
+ whenever(wmCallbacks.interceptKeyBeforeDispatching(any(), any(), anyInt())).thenReturn(0)
+
+ // Create a fallback for a key event with a meta modifier. Should result in -2,
+ // which represents the fallback event, which indicates that original key event will
+ // be ignored (not sent to app) and instead the fallback will be created and sent to the
+ // app.
+ val fallbackAction: KeyCharacterMap.FallbackAction = KeyCharacterMap.FallbackAction.obtain()
+ fallbackAction.keyCode = KeyEvent.KEYCODE_SEARCH
+ whenever(kcm.getFallbackAction(anyInt(), anyInt())).thenReturn(fallbackAction)
+
+ val event = KeyEvent( /* downTime= */0, /* eventTime= */0, KeyEvent.ACTION_DOWN,
+ KeyEvent.KEYCODE_SPACE, /* repeat= */0, KeyEvent.META_META_ON)
+ assertEquals(-2, service.interceptKeyBeforeDispatching(null, event, 0))
+ }
+
+ @Test
fun testKeyEventsNotForwardedToFocusedWindow_whenWmConsumes() {
service.systemRunning()
overrideSendActionKeyEventsToFocusedWindow(
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 4f1fb6487b19..163dda84a71c 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -63,6 +63,7 @@ import org.junit.After
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
+import org.junit.Assert.assertThrows
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
@@ -107,7 +108,10 @@ class KeyGestureControllerTests {
const val SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY = 0
const val SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL = 1
const val SETTINGS_KEY_BEHAVIOR_NOTHING = 2
+ const val SYSTEM_PID = 0
const val TEST_PID = 10
+ const val RANDOM_PID1 = 11
+ const val RANDOM_PID2 = 12
}
@JvmField
@@ -170,6 +174,7 @@ class KeyGestureControllerTests {
return atomicFile
}
})
+ startNewInputGlobalTestSession()
}
@After
@@ -199,17 +204,22 @@ class KeyGestureControllerTests {
val correctIm = context.getSystemService(InputManager::class.java)!!
val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
val kcm = virtualDevice.keyCharacterMap!!
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
- val inputManager = InputManager(context)
- Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
- .thenReturn(inputManager)
-
val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build()
Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm)
}
+ private fun startNewInputGlobalTestSession() {
+ if (this::inputManagerGlobalSession.isInitialized) {
+ inputManagerGlobalSession.close()
+ }
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
+ val inputManager = InputManager(context)
+ Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+ .thenReturn(inputManager)
+ }
+
private fun setupKeyGestureController() {
keyGestureController =
KeyGestureController(
@@ -225,13 +235,14 @@ class KeyGestureControllerTests {
return accessibilityShortcutController
}
})
- Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any()))
+ Mockito.`when`(iInputManager.registerKeyGestureHandler(Mockito.any(), Mockito.any()))
.thenAnswer {
val args = it.arguments
if (args[0] != null) {
keyGestureController.registerKeyGestureHandler(
- args[0] as IKeyGestureHandler,
- TEST_PID
+ args[0] as IntArray,
+ args[1] as IKeyGestureHandler,
+ SYSTEM_PID
)
}
}
@@ -285,59 +296,6 @@ class KeyGestureControllerTests {
)
}
- @Test
- fun testKeyGestureEvent_multipleGestureHandlers() {
- setupKeyGestureController()
-
- // Set up two callbacks.
- var callbackCount1 = 0
- var callbackCount2 = 0
- var selfCallback = 0
- val externalHandler1 = KeyGestureHandler { _, _ ->
- callbackCount1++
- true
- }
- val externalHandler2 = KeyGestureHandler { _, _ ->
- callbackCount2++
- true
- }
- val selfHandler = KeyGestureHandler { _, _ ->
- selfCallback++
- false
- }
-
- // Register key gesture handler: External process (last in priority)
- keyGestureController.registerKeyGestureHandler(externalHandler1, currentPid + 1)
-
- // Register key gesture handler: External process (second in priority)
- keyGestureController.registerKeyGestureHandler(externalHandler2, currentPid - 1)
-
- // Register key gesture handler: Self process (first in priority)
- keyGestureController.registerKeyGestureHandler(selfHandler, currentPid)
-
- keyGestureController.handleKeyGesture(/* deviceId = */ 0, intArrayOf(KeyEvent.KEYCODE_HOME),
- /* modifierState = */ 0, KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, /* displayId */ 0,
- /* focusedToken = */ null, /* flags = */ 0, /* appLaunchData = */null
- )
-
- assertEquals(
- "Self handler should get callbacks first",
- 1,
- selfCallback
- )
- assertEquals(
- "Higher priority handler should get callbacks first",
- 1,
- callbackCount2
- )
- assertEquals(
- "Lower priority handler should not get callbacks if already handled",
- 0,
- callbackCount1
- )
- }
-
class TestData(
val name: String,
val keys: IntArray,
@@ -789,10 +747,6 @@ class KeyGestureControllerTests {
)
fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) {
setupKeyGestureController()
- // Need to re-init so that bookmarks are correctly blocklisted
- Mockito.`when`(iInputManager.getAppLaunchBookmarks())
- .thenReturn(keyGestureController.appLaunchBookmarks)
- keyGestureController.systemRunning()
val builder = InputGestureData.Builder()
.setKeyGestureType(test.expectedKeyGestureType)
@@ -1163,9 +1117,6 @@ class KeyGestureControllerTests {
KeyEvent.KEYCODE_FULLSCREEN
)
- val handler = KeyGestureHandler { _, _ -> false }
- keyGestureController.registerKeyGestureHandler(handler, 0)
-
for (key in testKeys) {
sendKeys(intArrayOf(key), assertNotSentToApps = true)
}
@@ -1179,6 +1130,7 @@ class KeyGestureControllerTests {
testKeyGestureNotProduced(
"SEARCH -> Default Search",
intArrayOf(KeyEvent.KEYCODE_SEARCH),
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH)
)
}
@@ -1207,6 +1159,10 @@ class KeyGestureControllerTests {
testKeyGestureNotProduced(
"SETTINGS -> Do Nothing",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
+ intArrayOf(
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ )
)
}
@@ -1290,28 +1246,6 @@ class KeyGestureControllerTests {
)
),
TestData(
- "VOLUME_DOWN + VOLUME_UP -> Accessibility Chord",
- intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
- intArrayOf(KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_UP),
- 0,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE
- )
- ),
- TestData(
- "BACK + DPAD_DOWN -> Accessibility Chord(for TV)",
- intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT_CHORD,
- intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_DOWN),
- 0,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE
- )
- ),
- TestData(
"BACK + DPAD_CENTER -> TV Trigger Bug Report",
intArrayOf(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_DPAD_CENTER),
KeyGestureEvent.KEY_GESTURE_TYPE_TV_TRIGGER_BUG_REPORT,
@@ -1428,9 +1362,11 @@ class KeyGestureControllerTests {
testLooper.dispatchAll()
// Reinitialize the gesture controller simulating a login/logout for the user.
+ startNewInputGlobalTestSession()
setupKeyGestureController()
keyGestureController.setCurrentUserId(userId)
testLooper.dispatchAll()
+
val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
assertEquals(
"Test: $test doesn't produce correct number of saved input gestures",
@@ -1469,6 +1405,7 @@ class KeyGestureControllerTests {
// Delete the old data and reinitialize the controller simulating a "fresh" install.
tempFile.delete()
+ startNewInputGlobalTestSession()
setupKeyGestureController()
keyGestureController.setCurrentUserId(userId)
testLooper.dispatchAll()
@@ -1541,9 +1478,12 @@ class KeyGestureControllerTests {
val handledEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handledEvents.add(KeyGestureEvent(event))
- true
}
- keyGestureController.registerKeyGestureHandler(handler, 0)
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(test.expectedKeyGestureType),
+ handler,
+ TEST_PID
+ )
handledEvents.clear()
keyGestureController.handleTouchpadGesture(test.touchpadGestureType)
@@ -1570,7 +1510,7 @@ class KeyGestureControllerTests {
event.appLaunchData
)
- keyGestureController.unregisterKeyGestureHandler(handler, 0)
+ keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID)
}
@Test
@@ -1591,9 +1531,11 @@ class KeyGestureControllerTests {
testLooper.dispatchAll()
// Reinitialize the gesture controller simulating a login/logout for the user.
+ startNewInputGlobalTestSession()
setupKeyGestureController()
keyGestureController.setCurrentUserId(userId)
testLooper.dispatchAll()
+
val savedInputGestures = keyGestureController.getCustomInputGestures(userId, null)
assertEquals(
"Test: $test doesn't produce correct number of saved input gestures",
@@ -1627,6 +1569,7 @@ class KeyGestureControllerTests {
// Delete the old data and reinitialize the controller simulating a "fresh" install.
tempFile.delete()
+ startNewInputGlobalTestSession()
setupKeyGestureController()
keyGestureController.setCurrentUserId(userId)
testLooper.dispatchAll()
@@ -1699,13 +1642,97 @@ class KeyGestureControllerTests {
Mockito.verify(accessibilityShortcutController, never()).performAccessibilityShortcut()
}
+ @Test
+ fun testUnableToRegisterFromSamePidTwice() {
+ setupKeyGestureController()
+
+ val handler1 = KeyGestureHandler { _, _ -> }
+ val handler2 = KeyGestureHandler { _, _ -> }
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ handler1,
+ RANDOM_PID1
+ )
+
+ assertThrows(IllegalStateException::class.java) {
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_BACK),
+ handler2,
+ RANDOM_PID1
+ )
+ }
+ }
+
+ @Test
+ fun testUnableToRegisterSameGestureTwice() {
+ setupKeyGestureController()
+
+ val handler1 = KeyGestureHandler { _, _ -> }
+ val handler2 = KeyGestureHandler { _, _ -> }
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ handler1,
+ RANDOM_PID1
+ )
+
+ assertThrows(IllegalArgumentException::class.java) {
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_HOME),
+ handler2,
+ RANDOM_PID2
+ )
+ }
+ }
+
+ @Test
+ fun testUnableToRegisterEmptyListOfGestures() {
+ setupKeyGestureController()
+
+ val handler = KeyGestureHandler { _, _ -> }
+
+ assertThrows(IllegalArgumentException::class.java) {
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(),
+ handler,
+ RANDOM_PID1
+ )
+ }
+ }
+
+ @Test
+ fun testGestureHandlerNotCalledOnceUnregistered() {
+ setupKeyGestureController()
+
+ var callbackCount = 0
+ val handler1 = KeyGestureHandler { _, _ -> callbackCount++ }
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS),
+ handler1,
+ TEST_PID
+ )
+ sendKeys(intArrayOf(KeyEvent.KEYCODE_RECENT_APPS))
+ assertEquals(1, callbackCount)
+
+ keyGestureController.unregisterKeyGestureHandler(
+ handler1,
+ TEST_PID
+ )
+
+ // Callback should not be sent after unregister
+ sendKeys(intArrayOf(KeyEvent.KEYCODE_RECENT_APPS))
+ assertEquals(1, callbackCount)
+ }
+
private fun testKeyGestureInternal(test: TestData) {
val handledEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handledEvents.add(KeyGestureEvent(event))
- true
}
- keyGestureController.registerKeyGestureHandler(handler, 0)
+ keyGestureController.registerKeyGestureHandler(
+ intArrayOf(test.expectedKeyGestureType),
+ handler,
+ TEST_PID
+ )
handledEvents.clear()
sendKeys(test.keys)
@@ -1744,16 +1771,19 @@ class KeyGestureControllerTests {
)
}
- keyGestureController.unregisterKeyGestureHandler(handler, 0)
+ keyGestureController.unregisterKeyGestureHandler(handler, TEST_PID)
}
- private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
+ private fun testKeyGestureNotProduced(
+ testName: String,
+ testKeys: IntArray,
+ possibleGestures: IntArray
+ ) {
var handledEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handledEvents.add(KeyGestureEvent(event))
- true
}
- keyGestureController.registerKeyGestureHandler(handler, 0)
+ keyGestureController.registerKeyGestureHandler(possibleGestures, handler, TEST_PID)
handledEvents.clear()
sendKeys(testKeys)
@@ -1823,10 +1853,10 @@ class KeyGestureControllerTests {
}
inner class KeyGestureHandler(
- private var handler: (event: AidlKeyGestureEvent, token: IBinder?) -> Boolean
+ private var handler: (event: AidlKeyGestureEvent, token: IBinder?) -> Unit
) : IKeyGestureHandler.Stub() {
- override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?): Boolean {
- return handler(event, token)
+ override fun handleKeyGesture(event: AidlKeyGestureEvent, token: IBinder?) {
+ handler(event, token)
}
}
}
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index f8cb86b7b1fe..3ad3763a5d20 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -16,6 +16,7 @@
package com.android.test.input
import android.app.ActivityManager
+import android.app.ActivityTaskManager
import android.app.ApplicationExitInfo
import android.app.Instrumentation
import android.content.Intent
@@ -28,6 +29,7 @@ import android.os.SystemClock
import android.server.wm.CtsWindowInfoUtils.getWindowCenter
import android.server.wm.CtsWindowInfoUtils.waitForWindowOnTop
import android.testing.PollingCheck
+import android.util.Log
import android.view.InputEvent
import android.view.MotionEvent
import android.view.MotionEvent.ACTION_DOWN
@@ -46,21 +48,19 @@ import com.android.cts.input.inputeventmatchers.withMotionAction
import java.time.Duration
import java.util.concurrent.LinkedBlockingQueue
import java.util.function.Supplier
-import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Assert.fail
-import org.junit.Before
+import org.junit.Assume.assumeTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
/**
- * Click on the center of the window identified by the provided window token.
- * The click is performed using "UinputTouchScreen" device.
- * If the touchscreen device is closed too soon, it may cause the click to be dropped. Therefore,
- * the provided runnable can ensure that the click is delivered before the device is closed, thus
- * avoiding this race.
+ * Click on the center of the window identified by the provided window token. The click is performed
+ * using "UinputTouchScreen" device. If the touchscreen device is closed too soon, it may cause the
+ * click to be dropped. Therefore, the provided runnable can ensure that the click is delivered
+ * before the device is closed, thus avoiding this race.
*/
private fun clickOnWindow(
token: IBinder,
@@ -104,6 +104,10 @@ class AnrTest {
private val remoteInputEvents = LinkedBlockingQueue<InputEvent>()
private val verifier = BlockingQueueEventVerifier(remoteInputEvents)
+ // Some devices don't support ANR error dialogs, such as cars, TVs, etc.
+ private val anrDialogsAreSupported =
+ ActivityTaskManager.currentUiModeSupportsErrorDialogs(instrumentation.targetContext)
+
val binder =
object : IAnrTestService.Stub() {
override fun provideActivityInfo(token: IBinder, displayId: Int, pid: Int) {
@@ -121,34 +125,37 @@ class AnrTest {
@get:Rule val debugInputRule = DebugInputRule()
- @Before
- fun setUp() {
- startUnresponsiveActivity()
- PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName()
- }
-
- @After fun tearDown() {}
-
@Test
@DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Close() {
+ startUnresponsiveActivity()
+ val timestamp = System.currentTimeMillis()
triggerAnr()
- clickCloseAppOnAnrDialog()
+ if (anrDialogsAreSupported) {
+ clickCloseAppOnAnrDialog()
+ } else {
+ Log.i(TAG, "The device does not support ANR dialogs, skipping check for ANR window")
+ // We still want to wait for the app to get killed by the ActivityManager
+ }
+ waitForNewExitReasonAfter(timestamp)
}
@Test
@DebugInputRule.DebugInput(bug = 339924248)
fun testGestureMonitorAnr_Wait() {
+ assumeTrue(anrDialogsAreSupported)
+ startUnresponsiveActivity()
triggerAnr()
clickWaitOnAnrDialog()
SystemClock.sleep(500) // Wait at least 500ms after tapping on wait
// ANR dialog should reappear after a delay - find the close button on it to verify
+ val timestamp = System.currentTimeMillis()
clickCloseAppOnAnrDialog()
+ waitForNewExitReasonAfter(timestamp)
}
private fun clickCloseAppOnAnrDialog() {
// Find anr dialog and kill app
- val timestamp = System.currentTimeMillis()
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
val closeAppButton: UiObject2? =
uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")), 20000)
@@ -157,14 +164,6 @@ class AnrTest {
return
}
closeAppButton.click()
- /**
- * We must wait for the app to be fully closed before exiting this test. This is because
- * another test may again invoke 'am start' for the same activity. If the 1st process that
- * got ANRd isn't killed by the time second 'am start' runs, the killing logic will apply to
- * the newly launched 'am start' instance, and the second test will fail because the
- * unresponsive activity will never be launched.
- */
- waitForNewExitReasonAfter(timestamp)
}
private fun clickWaitOnAnrDialog() {
@@ -180,16 +179,27 @@ class AnrTest {
}
private fun getExitReasons(): List<ApplicationExitInfo> {
+ val packageName = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.name
lateinit var infos: List<ApplicationExitInfo>
instrumentation.runOnMainSync {
val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
- infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, remotePid!!, NO_MAX)
+ infos = am.getHistoricalProcessExitReasons(packageName, remotePid!!, NO_MAX)
}
return infos
}
+ /**
+ * We must wait for the app to be fully closed before exiting this test. This is because another
+ * test may again invoke 'am start' for the same activity. If the 1st process that got ANRd
+ * isn't killed by the time second 'am start' runs, the killing logic will apply to the newly
+ * launched 'am start' instance, and the second test will fail because the unresponsive activity
+ * will never be launched.
+ *
+ * Also, we must ensure that we wait until it's killed, so that the next test can launch this
+ * activity again.
+ */
private fun waitForNewExitReasonAfter(timestamp: Long) {
- PollingCheck.waitFor {
+ PollingCheck.waitFor(Duration.ofSeconds(20).toMillis() * Build.HW_TIMEOUT_MULTIPLIER) {
val reasons = getExitReasons()
!reasons.isEmpty() && reasons[0].timestamp >= timestamp
}
@@ -199,16 +209,15 @@ class AnrTest {
}
private fun triggerAnr() {
- clickOnWindow(
- remoteWindowToken!!,
- remoteDisplayId!!,
- instrumentation,
- ) { verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN)) }
+ clickOnWindow(remoteWindowToken!!, remoteDisplayId!!, instrumentation) {
+ verifier.assertReceivedMotion(withMotionAction(ACTION_DOWN))
+ }
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
private fun startUnresponsiveActivity() {
+ remoteWindowToken = null
val intent =
Intent(instrumentation.targetContext, UnresponsiveGestureMonitorActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NEW_DOCUMENT
@@ -218,12 +227,17 @@ class AnrTest {
instrumentation.targetContext.startActivity(intent)
// first, wait for the token to become valid
PollingCheck.check(
- "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
- Duration.ofSeconds(5).toMillis()) { remoteWindowToken != null }
+ "UnresponsiveGestureMonitorActivity failed to call 'provideActivityInfo'",
+ Duration.ofSeconds(10).toMillis() * Build.HW_TIMEOUT_MULTIPLIER,
+ ) {
+ remoteWindowToken != null
+ }
// next, wait for the window of the activity to get on top
// we could combine the two checks above, but the current setup makes it easier to detect
// errors
- assertTrue("Remote activity window did not become visible",
- waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }))
+ assertTrue(
+ "Remote activity window did not become visible",
+ waitForWindowOnTop(Duration.ofSeconds(5), Supplier { remoteWindowToken }),
+ )
}
}
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
index adefac64dbae..6846d489ecaa 100644
--- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -231,7 +231,7 @@ public class SmsApplicationTest {
.replacePreferredActivity(intentFilterCaptor.capture(),
eq(IntentFilter.MATCH_CATEGORY_SCHEME
| IntentFilter.MATCH_ADJUSTMENT_NORMAL),
- isNotNull(List.class),
+ (List<ComponentName>)isNotNull(),
eq(new ComponentName(TEST_COMPONENT_NAME.getPackageName(), SEND_TO_NAME)));
Set<String> capturedSchemes = intentFilterCaptor.getAllValues().stream()
diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto
index f4735a2f6ce7..380c5f21103c 100644
--- a/tools/aapt2/ResourcesInternal.proto
+++ b/tools/aapt2/ResourcesInternal.proto
@@ -50,8 +50,11 @@ message CompiledFile {
// Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file).
repeated Symbol exported_symbol = 5;
- // The status of the flag the file is behind if any
+ // The status of the read only flag the file is behind if any
uint32 flag_status = 6;
bool flag_negated = 7;
string flag_name = 8;
+
+ // Whether the file uses read/write feature flags
+ bool uses_readwrite_feature_flags = 9;
}
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index a5e18d35a256..3b4f5429f254 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -407,6 +407,45 @@ static bool IsValidFile(IAaptContext* context, const std::string& input_path) {
return true;
}
+class FindReadWriteFlagsVisitor : public xml::Visitor {
+ public:
+ FindReadWriteFlagsVisitor(const FeatureFlagValues& feature_flag_values)
+ : feature_flag_values_(feature_flag_values) {
+ }
+
+ void Visit(xml::Element* node) override {
+ if (had_flags_) {
+ return;
+ }
+ auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
+ if (attr != nullptr) {
+ std::string_view flag_name = util::TrimWhitespace(attr->value);
+ if (flag_name.starts_with('!')) {
+ flag_name = flag_name.substr(1);
+ }
+ if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
+ if (!it->second.read_only) {
+ had_flags_ = true;
+ return;
+ }
+ } else {
+ // Flag not passed to aapt2, must evaluate at runtime
+ had_flags_ = true;
+ return;
+ }
+ }
+ VisitChildren(node);
+ }
+
+ bool HadFlags() const {
+ return had_flags_;
+ }
+
+ private:
+ bool had_flags_ = false;
+ const FeatureFlagValues& feature_flag_values_;
+};
+
static bool CompileXml(IAaptContext* context, const CompileOptions& options,
const ResourcePathData& path_data, io::IFile* file, IArchiveWriter* writer,
const std::string& output_path) {
@@ -436,6 +475,10 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options,
xmlres->file.type = ResourceFile::Type::kProtoXml;
xmlres->file.flag = ParseFlag(path_data.flag_name);
+ FindReadWriteFlagsVisitor visitor(options.feature_flag_values);
+ xmlres->root->Accept(&visitor);
+ xmlres->file.uses_readwrite_feature_flags = visitor.HadFlags();
+
if (xmlres->file.flag) {
std::string error;
auto flag_status = GetFlagStatus(xmlres->file.flag, options.feature_flag_values, &error);
diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h
index 9452e588953e..5576ec0b882f 100644
--- a/tools/aapt2/cmd/Convert.h
+++ b/tools/aapt2/cmd/Convert.h
@@ -38,14 +38,14 @@ class ConvertCommand : public Command {
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&enable_sparse_encoding_);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &force_sparse_encoding_);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &force_sparse_encoding_);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 755dbb6f8e42..0e18ee250993 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -615,6 +615,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
file_op.xml_to_flatten->file.source = file_ref->GetSource();
file_op.xml_to_flatten->file.name =
ResourceName(pkg->name, type->named_type, entry->name);
+ file_op.xml_to_flatten->file.uses_readwrite_feature_flags =
+ config_value->uses_readwrite_feature_flags;
}
// NOTE(adamlesinski): Explicitly construct a StringPiece here, or
@@ -647,6 +649,17 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
}
}
+ FeatureFlagsFilterOptions flags_filter_options;
+ // Don't fail on unrecognized flags or flags without values as these flags might be
+ // defined and have a value by the time they are evaluated at runtime.
+ flags_filter_options.fail_on_unrecognized_flags = false;
+ flags_filter_options.flags_must_have_value = false;
+ flags_filter_options.remove_disabled_elements = true;
+ FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
+ if (!flags_filter.Consume(context_, file_op.xml_to_flatten.get())) {
+ return 1;
+ }
+
std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs =
LinkAndVersionXmlFile(table, &file_op);
if (versioned_docs.empty()) {
@@ -673,6 +686,7 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
// Update the output format of this XML file.
file_ref->type = XmlFileTypeForOutputFormat(options_.output_format);
+
bool result = table->AddResource(
NewResourceBuilder(file.name)
.SetValue(std::move(file_ref), file.config)
@@ -685,14 +699,6 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
}
}
- FeatureFlagsFilterOptions flags_filter_options;
- flags_filter_options.fail_on_unrecognized_flags = false;
- flags_filter_options.flags_must_have_value = false;
- FeatureFlagsFilter flags_filter(options_.feature_flag_values, flags_filter_options);
- if (!flags_filter.Consume(context_, doc.get())) {
- return 1;
- }
-
error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values,
false /*utf16*/, options_.output_format, archive_writer);
}
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 977978834fcd..54a8c8625eb5 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -164,9 +164,12 @@ class LinkCommand : public Command {
AddOptionalSwitch("--no-resource-removal", "Disables automatic removal of resources without\n"
"defaults. Use this only when building runtime resource overlay packages.",
&options_.no_resource_removal);
- AddOptionalSwitch("--enable-sparse-encoding",
- "This decreases APK size at the cost of resource retrieval performance.",
- &options_.use_sparse_encoding);
+ AddOptionalSwitch(
+ "--enable-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
+ &options_.use_sparse_encoding);
AddOptionalSwitch("--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
&options_.table_flattener_options.use_compact_entries);
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 012b0f230ca2..a8f547e3d96c 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -108,14 +108,14 @@ class OptimizeCommand : public Command {
"--enable-sparse-encoding",
"Enables encoding sparse entries using a binary search tree.\n"
"This decreases APK size at the cost of resource retrieval performance.\n"
- "Only applies sparse encoding to Android O+ resources or all resources if minSdk of "
- "the APK is O+",
+ "Only applies sparse encoding if minSdk of the APK is >= 32",
&options_.enable_sparse_encoding);
- AddOptionalSwitch("--force-sparse-encoding",
- "Enables encoding sparse entries using a binary search tree.\n"
- "This decreases APK size at the cost of resource retrieval performance.\n"
- "Applies sparse encoding to all resources regardless of minSdk.",
- &options_.force_sparse_encoding);
+ AddOptionalSwitch(
+ "--force-sparse-encoding",
+ "Enables encoding sparse entries using a binary search tree.\n"
+ "This decreases APK size at the cost of resource retrieval performance.\n"
+ "Only applies sparse encoding if minSdk of the APK is >= 32 or is not set",
+ &options_.force_sparse_encoding);
AddOptionalSwitch(
"--enable-compact-entries",
"This decreases APK size by using compact resource entries for simple data types.",
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 50144ae816b6..d19c9f2d5d75 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -197,13 +197,16 @@ class PackageFlattener {
bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled ||
sparse_entries_ == SparseEntriesMode::Forced;
- if (sparse_entries_ == SparseEntriesMode::Forced ||
- (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) {
- // Sparse encode if forced or sdk version is not set in context and config.
- } else {
- // Otherwise, only sparse encode if the entries will be read on platforms S_V2+.
- sparse_encode = sparse_encode && (context_->GetMinSdkVersion() >= SDK_S_V2);
- }
+ // Only sparse encode if the entries will be read on platforms S_V2+. Sparse encoding
+ // is not supported on older platforms (b/197642721, b/197976367).
+ //
+ // We also allow sparse encoding for minSdk is 0 (not set) if sparse encoding is forced,
+ // in order to support Bundletool's usage of aapt2 where minSdk is not set in splits.
+ bool meets_min_sdk_requirement_for_sparse_encoding =
+ (context_->GetMinSdkVersion() >= SDK_S_V2) ||
+ (context_->GetMinSdkVersion() == 0 && sparse_entries_ == SparseEntriesMode::Forced);
+
+ sparse_encode = sparse_encode && meets_min_sdk_requirement_for_sparse_encoding;
// Only sparse encode if the offsets are representable in 2 bytes.
sparse_encode = sparse_encode && short_offsets;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 9156b96b67ec..0e8aae14a350 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -15,6 +15,7 @@
*/
#include "format/binary/TableFlattener.h"
+#include <string>
#include "android-base/stringprintf.h"
#include "androidfw/TypeWrappers.h"
@@ -326,6 +327,28 @@ static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
return table;
}
+static void CheckSparseEntries(IAaptContext* context, const ConfigDescription& sparse_config,
+ const std::string& sparse_contents) {
+ ResourceTable sparse_table;
+ BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
+ sparse_contents.data(), sparse_contents.size());
+ ASSERT_TRUE(parser.Parse());
+
+ auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(0u, value->value.data);
+
+ ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
+ sparse_config),
+ IsNull());
+
+ value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
+ sparse_config);
+ ASSERT_THAT(value, NotNull());
+ EXPECT_EQ(4u, value->value.data);
+}
+
TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
@@ -347,29 +370,56 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2) {
EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
- // Attempt to parse the sparse contents.
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
+}
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkSV2AndForced) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_S_V2)
+ .Build();
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .SetCompilationPackage("android")
+ .SetPackageId(0x01)
+ .SetMinSdkVersion(SDK_LOLLIPOP)
+ .Build();
+
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
+
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Enabled;
+
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
+
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
+
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndConfigSdkVersionSV2) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -391,7 +441,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionSV2) {
EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
-TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkBeforeSV2AndForced) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder()
.SetCompilationPackage("android")
.SetPackageId(0x01)
@@ -410,7 +460,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) {
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
- EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
}
TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
@@ -429,28 +479,28 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) {
std::string sparse_contents;
ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
- EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+ EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
+}
- // Attempt to parse the sparse contents.
+TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSetAndForced) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("android").SetPackageId(0x01).Build();
- ResourceTable sparse_table;
- BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
- sparse_contents.data(), sparse_contents.size());
- ASSERT_TRUE(parser.Parse());
+ const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
+ auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
- auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(0u, value->value.data);
+ TableFlattenerOptions options;
+ options.sparse_entries = SparseEntriesMode::Forced;
- ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
- sparse_config),
- IsNull());
+ std::string no_sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
- value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
- sparse_config);
- ASSERT_THAT(value, NotNull());
- EXPECT_EQ(4u, value->value.data);
+ std::string sparse_contents;
+ ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
+
+ EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
+
+ CheckSparseEntries(context.get(), sparse_config, sparse_contents);
}
TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 91ec3485ac3b..b8936553a193 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -640,6 +640,7 @@ bool DeserializeCompiledFileFromPb(const pb::internal::CompiledFile& pb_file,
out_file->name = name_ref.ToResourceName();
out_file->source.path = pb_file.source_path();
out_file->type = DeserializeFileReferenceTypeFromPb(pb_file.type());
+ out_file->uses_readwrite_feature_flags = pb_file.uses_readwrite_feature_flags();
out_file->flag_status = (FlagStatus)pb_file.flag_status();
if (!pb_file.flag_name().empty()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index fcc77d5a9d6d..da99c4f5917c 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -767,6 +767,7 @@ void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledF
out_file->set_flag_negated(file.flag->negated);
out_file->set_flag_name(file.flag->name);
}
+ out_file->set_uses_readwrite_feature_flags(file.uses_readwrite_feature_flags);
for (const SourcedResourceName& exported : file.exported_symbols) {
pb::internal::CompiledFile_Symbol* pb_symbol = out_file->add_exported_symbol();
diff --git a/tools/aapt2/link/FlaggedResources_test.cpp b/tools/aapt2/link/FlaggedResources_test.cpp
index 47a71fe36e9f..4dcb8507fa45 100644
--- a/tools/aapt2/link/FlaggedResources_test.cpp
+++ b/tools/aapt2/link/FlaggedResources_test.cpp
@@ -226,9 +226,11 @@ TEST_F(FlaggedResourcesTest, ReadWriteFlagInXmlGetsFlagged) {
}
}
}
+
ASSERT_TRUE(found) << "No entry for layout1 at v36 with FLAG_USES_FEATURE_FLAGS bit set";
- // There should only be 1 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1
- ASSERT_EQ(fields_flagged, 1);
+ // There should only be 2 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1, the
+ // three versions of the layout file that has flags
+ ASSERT_EQ(fields_flagged, 3);
}
} // namespace aapt
diff --git a/tools/aapt2/link/FlaggedXmlVersioner.cpp b/tools/aapt2/link/FlaggedXmlVersioner.cpp
index 8a3337c446cb..626cae73bfa2 100644
--- a/tools/aapt2/link/FlaggedXmlVersioner.cpp
+++ b/tools/aapt2/link/FlaggedXmlVersioner.cpp
@@ -35,10 +35,6 @@ class AllDisabledFlagsVisitor : public xml::Visitor {
VisitChildren(node);
}
- bool HadFlags() const {
- return had_flags_;
- }
-
private:
bool FixupOrShouldRemove(const std::unique_ptr<xml::Node>& node) {
if (auto* el = NodeCast<Element>(node.get())) {
@@ -47,7 +43,6 @@ class AllDisabledFlagsVisitor : public xml::Visitor {
return false;
}
- had_flags_ = true;
// This class assumes all flags are disabled so we want to remove any elements behind flags
// unless the flag specification is negated. In the negated case we remove the featureFlag
// attribute because we have already determined whether we are keeping the element or not.
@@ -62,56 +57,27 @@ class AllDisabledFlagsVisitor : public xml::Visitor {
return false;
}
-
- bool had_flags_ = false;
-};
-
-// An xml visitor that goes through the a doc and determines if any elements are behind a flag.
-class FindFlagsVisitor : public xml::Visitor {
- public:
- void Visit(xml::Element* node) override {
- if (had_flags_) {
- return;
- }
- auto* attr = node->FindAttribute(xml::kSchemaAndroid, xml::kAttrFeatureFlag);
- if (attr != nullptr) {
- had_flags_ = true;
- return;
- }
- VisitChildren(node);
- }
-
- bool HadFlags() const {
- return had_flags_;
- }
-
- bool had_flags_ = false;
};
std::vector<std::unique_ptr<xml::XmlResource>> FlaggedXmlVersioner::Process(IAaptContext* context,
xml::XmlResource* doc) {
std::vector<std::unique_ptr<xml::XmlResource>> docs;
- if ((static_cast<ApiVersion>(doc->file.config.sdkVersion) >= SDK_BAKLAVA) ||
- (static_cast<ApiVersion>(context->GetMinSdkVersion()) >= SDK_BAKLAVA)) {
+ if (!doc->file.uses_readwrite_feature_flags) {
+ docs.push_back(doc->Clone());
+ } else if ((static_cast<ApiVersion>(doc->file.config.sdkVersion) >= SDK_BAKLAVA) ||
+ (static_cast<ApiVersion>(context->GetMinSdkVersion()) >= SDK_BAKLAVA)) {
// Support for read/write flags was added in baklava so if the doc will only get used on
// baklava or later we can just return the original doc.
docs.push_back(doc->Clone());
- FindFlagsVisitor visitor;
- doc->root->Accept(&visitor);
- docs.back()->file.uses_readwrite_feature_flags = visitor.HadFlags();
} else {
auto preBaklavaVersion = doc->Clone();
AllDisabledFlagsVisitor visitor;
preBaklavaVersion->root->Accept(&visitor);
- preBaklavaVersion->file.uses_readwrite_feature_flags = false;
docs.push_back(std::move(preBaklavaVersion));
- if (visitor.HadFlags()) {
- auto baklavaVersion = doc->Clone();
- baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
- baklavaVersion->file.uses_readwrite_feature_flags = true;
- docs.push_back(std::move(baklavaVersion));
- }
+ auto baklavaVersion = doc->Clone();
+ baklavaVersion->file.config.sdkVersion = SDK_BAKLAVA;
+ docs.push_back(std::move(baklavaVersion));
}
return docs;
}
diff --git a/tools/aapt2/link/FlaggedXmlVersioner_test.cpp b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
index 0c1314f165cc..0dc464253385 100644
--- a/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
+++ b/tools/aapt2/link/FlaggedXmlVersioner_test.cpp
@@ -101,6 +101,7 @@ TEST_F(FlaggedXmlVersionerTest, PreBaklavaGetsSplit) {
<TextView android:featureFlag="package.flag" /><TextView /><TextView />
</LinearLayout>)");
doc->file.config.sdkVersion = SDK_GINGERBREAD;
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -131,6 +132,7 @@ TEST_F(FlaggedXmlVersionerTest, NoVersionGetsSplit) {
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:featureFlag="package.flag" /><TextView /><TextView />
</LinearLayout>)");
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -162,6 +164,7 @@ TEST_F(FlaggedXmlVersionerTest, NegatedFlagAttributeRemoved) {
<TextView android:featureFlag="!package.flag" /><TextView /><TextView />
</LinearLayout>)");
doc->file.config.sdkVersion = SDK_GINGERBREAD;
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
@@ -192,6 +195,7 @@ TEST_F(FlaggedXmlVersionerTest, NegatedFlagAttributeRemovedNoSpecifiedVersion) {
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<TextView android:featureFlag="!package.flag" /><TextView /><TextView />
</LinearLayout>)");
+ doc->file.uses_readwrite_feature_flags = true;
FlaggedXmlVersioner versioner;
auto results = versioner.Process(context_.get(), doc.get());
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1d4adc4a57d8..17f332397317 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -295,6 +295,8 @@ bool TableMerger::DoMerge(const android::Source& src, ResourceTablePackage* src_
dst_config_value =
dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
}
+ dst_config_value->uses_readwrite_feature_flags |=
+ src_config_value->uses_readwrite_feature_flags;
// Continue if we're taking the new resource.
CloningValueTransformer cloner(&main_table_->string_pool);
@@ -378,12 +380,13 @@ bool TableMerger::MergeFile(const ResourceFile& file_desc, bool overlay, io::IFi
file_ref->file = file;
file_ref->SetFlagStatus(file_desc.flag_status);
file_ref->SetFlag(file_desc.flag);
-
ResourceTablePackage* pkg = table.FindOrCreatePackage(file_desc.name.package);
- pkg->FindOrCreateType(file_desc.name.type)
- ->FindOrCreateEntry(file_desc.name.entry)
- ->FindOrCreateValue(file_desc.config, {})
- ->value = std::move(file_ref);
+ ResourceConfigValue* config_value = pkg->FindOrCreateType(file_desc.name.type)
+ ->FindOrCreateEntry(file_desc.name.entry)
+ ->FindOrCreateValue(file_desc.config, {});
+
+ config_value->value = std::move(file_ref);
+ config_value->uses_readwrite_feature_flags = file_desc.uses_readwrite_feature_flags;
return DoMerge(file->GetSource(), pkg, false /*mangle*/, overlay /*overlay*/, true /*allow_new*/);
}
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
index 6bdbaaed9858..413f817ea8fd 100644
--- a/tools/aapt2/readme.md
+++ b/tools/aapt2/readme.md
@@ -5,6 +5,10 @@
2017. This README will be updated more frequently in the future.
- Added a new flag `--no-compress-fonts`. This can significantly speed up loading fonts from APK
assets, at the cost of increasing the storage size of the APK.
+- Changed the behavior of `--enable-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32.
+- Changed the behavior of `--force-sparse-encoding`. Sparse encoding is only applied if the
+ minSdkVersion is >= 32 or is not set.
## Version 2.19
- Added navigation resource type.
diff --git a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt b/tools/protologtool/src/com/android/protolog/tool/Exceptions.kt
index ae00df123353..4f7e8d13c4ba 100644
--- a/tools/protologtool/src/com/android/protolog/tool/exceptions.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/Exceptions.kt
@@ -18,21 +18,24 @@ package com.android.protolog.tool
import java.lang.Exception
-open class CodeProcessingException(message: String, context: ParsingContext)
- : Exception("Code processing error in ${context.filePath}:${context.lineNumber}:\n" +
- " $message")
+open class CodeProcessingException(
+ message: String, context: ParsingContext, cause: Throwable? = null
+) : Exception("Code processing error in ${context.filePath}:${context.lineNumber}:\n" +
+ " $message", cause)
-class HashCollisionException(message: String, context: ParsingContext) :
- CodeProcessingException(message, context)
+class HashCollisionException(
+ message: String, context: ParsingContext, cause: Throwable? = null
+) : CodeProcessingException(message, context, cause)
-class IllegalImportException(message: String, context: ParsingContext) :
- CodeProcessingException("Illegal import: $message", context)
+class IllegalImportException(message: String, context: ParsingContext, cause: Throwable? = null) :
+ CodeProcessingException("Illegal import: $message", context, cause)
-class InvalidProtoLogCallException(message: String, context: ParsingContext)
- : CodeProcessingException("InvalidProtoLogCall: $message", context)
+class InvalidProtoLogCallException(
+ message: String, context: ParsingContext, cause: Throwable? = null
+) : CodeProcessingException("InvalidProtoLogCall: $message", context, cause)
-class ParsingException(message: String, context: ParsingContext)
- : CodeProcessingException(message, context)
+class ParsingException(message: String, context: ParsingContext, cause: Throwable? = null) :
+ CodeProcessingException(message, context, cause)
class InvalidViewerConfigException(message: String) : Exception(message)
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 272d8bb1793d..1f6db5fe2d37 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -118,9 +118,17 @@ class ProtoLogCallProcessorImpl(
"- not a ProtoLogGroup enum member: $call", context)
}
- logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
- call.name.toString(), call, context), groupMap.getValue(groupName),
- context.lineNumber)
+ try {
+ logCallVisitor?.processCall(
+ call, messageString, getLevelForMethodName(
+ call.name.toString(), call, context
+ ), groupMap.getValue(groupName),
+ context.lineNumber
+ )
+ } catch (e: Throwable) {
+ throw InvalidProtoLogCallException("Error processing log call: $call",
+ context, e)
+ }
} else if (call.name.id == "init") {
// No processing
} else {
diff --git a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
index 004d97babbad..732824ae841c 100644
--- a/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
+++ b/tools/protologtool/tests/com/android/protolog/tool/ProtoLogCallProcessorImplTest.kt
@@ -16,11 +16,14 @@
package com.android.protolog.tool
+import com.android.internal.protolog.common.InvalidFormatStringException
import com.android.internal.protolog.common.LogLevel
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.expr.MethodCallExpr
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
import org.junit.Test
+import com.google.common.truth.Truth
class ProtoLogCallProcessorImplTest {
private data class LogCall(
@@ -228,4 +231,38 @@ class ProtoLogCallProcessorImplTest {
visitor.process(StaticJavaParser.parse(code), processor, "")
checkCalls()
}
+
+ @Test
+ fun throws_clear_error_message_on_invalid_format_exception() {
+ val code = """
+ package org.example;
+
+ class Test {
+ void test() {
+ ProtoLog.d(ProtoLogGroup.TEST, "Invalid message %9 %");
+ }
+ }
+ """
+ groupMap["TEST"] = LogGroup("TEST", false, true, "WindowManager")
+
+ val processor = object : ProtoLogCallVisitor {
+ override fun processCall(
+ call: MethodCallExpr,
+ messageString: String,
+ level: LogLevel,
+ group: LogGroup,
+ lineNumber: Int,
+ ) {
+ throw InvalidFormatStringException("Invalid Protolog message format")
+ }
+ }
+
+ val exception = assertThrows(InvalidProtoLogCallException::class.java) {
+ visitor.process(StaticJavaParser.parse(code), processor, "MyTestFile.java")
+ }
+ Truth.assertThat(exception).hasMessageThat()
+ .contains("Code processing error in MyTestFile.java:6")
+ Truth.assertThat(exception.cause).hasMessageThat()
+ .contains("Invalid Protolog message format")
+ }
}