summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp4
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java4
-rw-r--r--api/api.go5
-rw-r--r--core/java/android/app/ApplicationStartInfo.java8
-rw-r--r--core/java/android/app/NotificationManager.java75
-rw-r--r--core/java/android/app/notification.aconfig17
-rw-r--r--core/java/android/app/supervision/ISupervisionAppService.aidl (renamed from packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl)7
-rw-r--r--core/java/android/app/supervision/SupervisionAppService.java (renamed from packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl)27
-rw-r--r--core/java/android/app/supervision/flags.aconfig8
-rw-r--r--core/java/android/os/TEST_MAPPING26
-rw-r--r--core/java/android/provider/Settings.java13
-rw-r--r--core/java/android/security/flags.aconfig9
-rw-r--r--core/java/android/service/settings/preferences/SettingsPreferenceValue.java103
-rw-r--r--core/java/android/text/Layout.java6
-rw-r--r--core/java/android/window/TaskFragmentOperation.java11
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig16
-rw-r--r--core/java/com/android/internal/jank/Cuj.java16
-rw-r--r--core/java/com/android/internal/os/RuntimeInit.java4
-rw-r--r--core/res/AndroidManifest.xml21
-rw-r--r--core/res/res/values/config.xml10
-rw-r--r--core/res/res/values/symbols.xml2
-rw-r--r--core/tests/coretests/src/android/text/LayoutTest.java49
-rw-r--r--data/etc/preinstalled-packages-platform.xml2
-rw-r--r--keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java11
-rw-r--r--libs/WindowManager/Shell/Android.bp2
-rw-r--r--libs/WindowManager/Shell/aconfig/Android.bp1
-rw-r--r--libs/WindowManager/Shell/aconfig/automotive.aconfig11
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt3
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml2
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt293
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt611
-rw-r--r--media/java/android/media/MediaRoute2ProviderService.java18
-rw-r--r--media/java/android/media/tv/flags/media_tv.aconfig10
-rw-r--r--media/jni/android_media_tv_Tuner.cpp26
-rw-r--r--native/android/TEST_MAPPING16
-rw-r--r--native/graphics/jni/Android.bp7
-rw-r--r--nfc-non-updatable/flags/flags.aconfig8
-rw-r--r--packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java359
-rw-r--r--packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS3
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java447
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java2253
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java861
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java58
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java85
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java785
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java255
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java42
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java117
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java188
-rw-r--r--packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java66
-rw-r--r--packages/EasterEgg/AndroidManifest.xml6
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive.xml21
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml245
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml35
-rw-r--r--packages/EasterEgg/res/drawable/android16_patch_monochrome.xml113
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt2
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt31
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/Physics.kt24
-rw-r--r--packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt118
-rw-r--r--packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml28
-rw-r--r--packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml35
-rw-r--r--packages/SettingsLib/CardPreference/res/values/styles_expressive.xml4
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/aconfig/settingslib.aconfig10
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java16
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java4
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java69
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig10
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java81
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/aconfig/Android.bp1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java12
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt33
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt228
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt24
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt33
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt (renamed from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt)26
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt14
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt6
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt94
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt168
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt77
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive.xml21
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml245
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml35
-rw-r--r--packages/SystemUI/res/drawable/android16_patch_monochrome.xml113
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java8
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt60
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/OWNERS12
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java115
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt63
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt19
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt14
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt17
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt4
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java31
-rw-r--r--packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java2
-rw-r--r--services/core/Android.bp2
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java3
-rw-r--r--services/core/java/com/android/server/OWNERS1
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java18
-rw-r--r--services/core/java/com/android/server/ZramMaintenance.java118
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java1
-rw-r--r--services/core/java/com/android/server/crashrecovery/OWNERS1
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java67
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java10
-rw-r--r--services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java49
-rw-r--r--services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java8
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java49
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java25
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig7
-rw-r--r--services/core/java/com/android/server/pm/BroadcastHelper.java5
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java14
-rw-r--r--services/core/java/com/android/server/power/hint/TEST_MAPPING17
-rw-r--r--services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java13
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivitySnapshotController.java16
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java6
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java4
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOverrides.java10
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java20
-rw-r--r--services/core/java/com/android/server/wm/DragDropController.java4
-rw-r--r--services/core/java/com/android/server/wm/DragState.java39
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java7
-rw-r--r--services/core/java/com/android/server/wm/Task.java18
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java24
-rw-r--r--services/core/java/com/android/server/wm/TaskPersister.java6
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java13
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotPersister.java15
-rw-r--r--services/core/java/com/android/server/wm/Transition.java7
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerFlags.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java18
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java83
-rw-r--r--services/core/java/com/android/server/wm/WindowStateAnimator.java54
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfacePlacer.java1
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java56
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java17
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java41
-rw-r--r--services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig10
-rw-r--r--services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java76
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java48
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java24
-rw-r--r--services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java57
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java48
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java3
-rw-r--r--tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java6
256 files changed, 4565 insertions, 7356 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index a60ced5835ea..f249884cb1a0 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -107,7 +107,7 @@ aconfig_declarations_group {
"com.android.server.flags.services-aconfig-java",
"com.android.text.flags-aconfig-java",
"com.android.window.flags.window-aconfig-java",
- "configinfra_framework_flags_java_lib",
+ "configinfra_framework_flags_java_exported_lib",
"conscrypt_exported_aconfig_flags_lib",
"device_policy_aconfig_flags_lib",
"display_flags_lib",
@@ -467,7 +467,7 @@ java_aconfig_library {
"//apex_available:platform",
"com.android.art",
"com.android.art.debug",
- "com.android.btservices",
+ "com.android.bt",
"com.android.mediaprovider",
"com.android.permission",
],
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index 60ba3b896a28..829442aed6ac 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -96,6 +96,7 @@ import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserPackage;
+import android.content.res.Resources;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.BatteryStatsInternal;
@@ -1784,7 +1785,8 @@ public class AlarmManagerService extends SystemService {
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
mStartUserBeforeScheduledAlarms = Flags.startUserBeforeScheduledAlarms()
- && UserManager.supportsMultipleUsers();
+ && UserManager.supportsMultipleUsers() && Resources.getSystem().getBoolean(
+ com.android.internal.R.bool.config_allowAlarmsOnStoppedUsers);
if (mStartUserBeforeScheduledAlarms) {
mUserWakeupStore = new UserWakeupStore();
mUserWakeupStore.init();
diff --git a/api/api.go b/api/api.go
index cbdb7e81ab86..640773be0f9b 100644
--- a/api/api.go
+++ b/api/api.go
@@ -104,8 +104,9 @@ func (a *CombinedApis) DepsMutator(ctx android.BottomUpMutatorContext) {
}
func (a *CombinedApis) GenerateAndroidBuildActions(ctx android.ModuleContext) {
- ctx.WalkDeps(func(child, parent android.Module) bool {
- if _, ok := android.OtherModuleProvider(ctx, child, java.AndroidLibraryInfoProvider); ok && child.Name() != "framework-res" {
+ ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+ javaInfo, ok := android.OtherModuleProvider(ctx, child, java.JavaInfoProvider)
+ if ok && javaInfo.AndroidLibraryDependencyInfo != nil && child.Name() != "framework-res" {
// Stubs of BCP and SSCP libraries should not have any dependencies on apps
// This check ensures that we do not run into circular dependencies when UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT=true
ctx.ModuleErrorf(
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index f34341fd14e1..3214bd8f01fc 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -729,6 +729,7 @@ public final class ApplicationStartInfo implements Parcelable {
return 0;
}
+ // LINT.IfChange(write_parcel)
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mStartupState);
@@ -753,6 +754,7 @@ public final class ApplicationStartInfo implements Parcelable {
dest.writeLong(mMonoticCreationTimeMs);
dest.writeInt(mStartComponent);
}
+ // LINT.ThenChange(:read_parcel)
/** @hide */
public ApplicationStartInfo(long monotonicCreationTimeMs) {
@@ -779,6 +781,7 @@ public final class ApplicationStartInfo implements Parcelable {
}
/** @hide */
+ // LINT.IfChange(read_parcel)
@VisibleForTesting
public ApplicationStartInfo(@NonNull Parcel in) {
mStartupState = in.readInt();
@@ -803,6 +806,7 @@ public final class ApplicationStartInfo implements Parcelable {
mMonoticCreationTimeMs = in.readLong();
mStartComponent = in.readInt();
}
+ // LINT.ThenChange(:write_parcel)
private static String intern(@Nullable String source) {
return source != null ? source.intern() : null;
@@ -835,6 +839,7 @@ public final class ApplicationStartInfo implements Parcelable {
* @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
* @hide
*/
+ // LINT.IfChange(write_proto)
public void writeToProto(ProtoOutputStream proto, long fieldId) throws IOException {
final long token = proto.start(fieldId);
proto.write(ApplicationStartInfoProto.PID, mPid);
@@ -884,6 +889,7 @@ public final class ApplicationStartInfo implements Parcelable {
proto.write(ApplicationStartInfoProto.START_COMPONENT, mStartComponent);
proto.end(token);
}
+ // LINT.ThenChange(:read_proto)
/**
* Read from a protocol buffer input stream. Protocol buffer message definition at {@link
@@ -893,6 +899,7 @@ public final class ApplicationStartInfo implements Parcelable {
* @param fieldId Field Id of the ApplicationStartInfo as defined in the parent message
* @hide
*/
+ // LINT.IfChange(read_proto)
public void readFromProto(ProtoInputStream proto, long fieldId)
throws IOException, WireTypeMismatchException, ClassNotFoundException {
final long token = proto.start(fieldId);
@@ -976,6 +983,7 @@ public final class ApplicationStartInfo implements Parcelable {
}
proto.end(token);
}
+ // LINT.ThenChange(:write_proto)
/** @hide */
public void dump(@NonNull PrintWriter pw, @Nullable String prefix, @Nullable String seqSuffix,
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d22926791cc3..24f2495d8f09 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -68,6 +68,7 @@ import android.service.notification.StatusBarNotification;
import android.service.notification.ZenDeviceEffects;
import android.service.notification.ZenModeConfig;
import android.service.notification.ZenPolicy;
+import android.text.TextUtils;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
@@ -77,6 +78,8 @@ import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.time.Duration;
+import java.time.Instant;
import java.time.InstantSource;
import java.util.ArrayList;
import java.util.Arrays;
@@ -661,8 +664,10 @@ public class NotificationManager {
mCallNotificationEventCallbacks = new HashMap<>();
private final InstantSource mClock;
- private final RateEstimator mUpdateRateEstimator = new RateEstimator();
- private final RateEstimator mUnnecessaryCancelRateEstimator = new RateEstimator();
+ private final RateLimiter mUpdateRateLimiter = new RateLimiter("notify (update)",
+ MAX_NOTIFICATION_UPDATE_RATE);
+ private final RateLimiter mUnnecessaryCancelRateLimiter = new RateLimiter("cancel (dupe)",
+ MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE);
// Value is KNOWN_STATUS_ENQUEUED/_CANCELLED
private final LruCache<NotificationKey, Integer> mKnownNotifications = new LruCache<>(100);
private final Object mThrottleLock = new Object();
@@ -820,21 +825,16 @@ public class NotificationManager {
if (Flags.nmBinderPerfThrottleNotify()) {
NotificationKey key = new NotificationKey(user, pkg, tag, id);
- long now = mClock.millis();
synchronized (mThrottleLock) {
Integer status = mKnownNotifications.get(key);
if (status != null && status == KNOWN_STATUS_ENQUEUED
&& !notification.hasCompletedProgress()) {
- float updateRate = mUpdateRateEstimator.getRate(now);
- if (updateRate > MAX_NOTIFICATION_UPDATE_RATE) {
- Slog.w(TAG, "Shedding update of " + key
- + ", notification update maximum rate exceeded (" + updateRate
- + ")");
+ if (mUpdateRateLimiter.eventExceedsRate()) {
+ mUpdateRateLimiter.recordRejected(key);
return true;
}
- mUpdateRateEstimator.update(now);
+ mUpdateRateLimiter.recordAccepted();
}
-
mKnownNotifications.put(key, KNOWN_STATUS_ENQUEUED);
}
}
@@ -845,6 +845,51 @@ public class NotificationManager {
private record NotificationKey(@NonNull UserHandle user, @NonNull String pkg,
@Nullable String tag, int id) { }
+ /** Helper class to rate-limit Binder calls. */
+ private class RateLimiter {
+
+ private static final Duration RATE_LIMITER_LOG_INTERVAL = Duration.ofSeconds(5);
+
+ private final RateEstimator mInputRateEstimator;
+ private final RateEstimator mOutputRateEstimator;
+ private final String mName;
+ private final float mLimitRate;
+
+ private Instant mLogSilencedUntil;
+
+ private RateLimiter(String name, float limitRate) {
+ mInputRateEstimator = new RateEstimator();
+ mOutputRateEstimator = new RateEstimator();
+ mName = name;
+ mLimitRate = limitRate;
+ }
+
+ boolean eventExceedsRate() {
+ long nowMillis = mClock.millis();
+ mInputRateEstimator.update(nowMillis);
+ return mOutputRateEstimator.getRate(nowMillis) > mLimitRate;
+ }
+
+ void recordAccepted() {
+ mOutputRateEstimator.update(mClock.millis());
+ }
+
+ void recordRejected(NotificationKey key) {
+ Instant now = mClock.instant();
+ if (mLogSilencedUntil != null && now.isBefore(mLogSilencedUntil)) {
+ return;
+ }
+
+ long nowMillis = now.toEpochMilli();
+ Slog.w(TAG, TextUtils.formatSimple(
+ "Shedding %s of %s, rate limit (%s) exceeded: input %s, output would be %s",
+ mName, key, mLimitRate, mInputRateEstimator.getRate(nowMillis),
+ mOutputRateEstimator.getRate(nowMillis)));
+
+ mLogSilencedUntil = now.plus(RATE_LIMITER_LOG_INTERVAL);
+ }
+ }
+
private Notification fixNotification(Notification notification) {
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
@@ -967,18 +1012,14 @@ public class NotificationManager {
private boolean discardCancel(UserHandle user, String pkg, @Nullable String tag, int id) {
if (Flags.nmBinderPerfThrottleNotify()) {
NotificationKey key = new NotificationKey(user, pkg, tag, id);
- long now = mClock.millis();
synchronized (mThrottleLock) {
Integer status = mKnownNotifications.get(key);
if (status != null && status == KNOWN_STATUS_CANCELLED) {
- float cancelRate = mUnnecessaryCancelRateEstimator.getRate(now);
- if (cancelRate > MAX_NOTIFICATION_UNNECESSARY_CANCEL_RATE) {
- Slog.w(TAG, "Shedding cancel of " + key
- + ", presumably unnecessary and maximum rate exceeded ("
- + cancelRate + ")");
+ if (mUnnecessaryCancelRateLimiter.eventExceedsRate()) {
+ mUnnecessaryCancelRateLimiter.recordRejected(key);
return true;
}
- mUnnecessaryCancelRateEstimator.update(now);
+ mUnnecessaryCancelRateLimiter.recordAccepted();
}
mKnownNotifications.put(key, KNOWN_STATUS_CANCELLED);
}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index b1db1379e400..edd17e8bb552 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -13,6 +13,13 @@ flag {
}
flag {
+ name: "notifications_redesign_themed_app_icons"
+ namespace: "systemui"
+ description: "Notifications Redesign: Experiment to make app icons in notifications themed"
+ bug: "371174789"
+}
+
+flag {
name: "notifications_redesign_templates"
namespace: "systemui"
description: "Notifications Redesign: Update notification templates"
@@ -174,16 +181,6 @@ flag {
}
flag {
- name: "update_ranking_time"
- namespace: "systemui"
- description: "Updates notification sorting criteria to highlight new content while maintaining stability"
- bug: "326016985"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "sort_section_by_time"
namespace: "systemui"
description: "Changes notification sort order to be by time within a section"
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/core/java/android/app/supervision/ISupervisionAppService.aidl
index 013158676f79..033998fc4a5b 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
+++ b/core/java/android/app/supervision/ISupervisionAppService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package android.service.watchdog;
+package android.app.supervision;
/**
* @hide
*/
-parcelable PackageConfig;
+interface ISupervisionAppService {
+}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/core/java/android/app/supervision/SupervisionAppService.java
index 90965092ac2b..4468c78cbd34 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
+++ b/core/java/android/app/supervision/SupervisionAppService.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,19 +14,24 @@
* limitations under the License.
*/
-package android.service.watchdog;
+package android.app.supervision;
-import android.os.RemoteCallback;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
/**
+ * Base class for a service that the {@code android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION}
+ * role holder must implement.
+ *
* @hide
*/
-@PermissionManuallyEnforced
-oneway interface IExplicitHealthCheckService
-{
- void setCallback(in @nullable RemoteCallback callback);
- void request(String packageName);
- void cancel(String packageName);
- void getSupportedPackages(in RemoteCallback callback);
- void getRequestedPackages(in RemoteCallback callback);
+public class SupervisionAppService extends Service {
+ private final ISupervisionAppService mBinder = new ISupervisionAppService.Stub() {
+ };
+
+ @Override
+ public final IBinder onBind(Intent intent) {
+ return mBinder.asBinder();
+ }
}
diff --git a/core/java/android/app/supervision/flags.aconfig b/core/java/android/app/supervision/flags.aconfig
index 1b0353274fb9..4ee3a0360b43 100644
--- a/core/java/android/app/supervision/flags.aconfig
+++ b/core/java/android/app/supervision/flags.aconfig
@@ -32,3 +32,11 @@ flag {
description: "Flag that deprecates supervision methods in DPM"
bug: "382034839"
}
+
+flag {
+ name: "enable_supervision_app_service"
+ is_exported: true
+ namespace: "supervision"
+ description: "Flag to enable the SupervisionAppService"
+ bug: "389123070"
+}
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index 48fa0c8277df..effe5554aff4 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -112,32 +112,6 @@
{
"file_patterns": ["Bugreport[^/]*\\.java"],
"name": "ShellTests"
- },
- {
- "file_patterns": [
- "CpuHeadroom[^/]*",
- "GpuHeadroom[^/]*",
- "health/SystemHealthManager\\.java"
- ],
- "name": "CtsOsTestCases",
- "options": [
- {"include-filter": "android.os.health.cts.HeadroomTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- },
- {
- "file_patterns": [
- "CpuHeadroom[^/]*",
- "GpuHeadroom[^/]*",
- "health/SystemHealthManager\\.java"
- ],
- "name": "FrameworksCoreTests",
- "options": [
- {"include-filter": "android.os.SystemHealthManagerUnitTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
}
],
"ravenwood-presubmit": [
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 73d1e1701eec..0cfec2cc7314 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -12869,19 +12869,6 @@ public final class Settings {
*/
public static final String DISABLE_SECURE_WINDOWS = "disable_secure_windows";
- /**
- * Controls if the adaptive authentication feature should be disabled, which
- * will attempt to lock the device after a number of consecutive authentication
- * attempts fail.
- *
- * This can only be disabled on debuggable builds. Set to 1 to disable or 0 for the
- * normal behavior.
- *
- * @hide
- */
- public static final String DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK =
- "disable_adaptive_auth_limit_lock";
-
/** @hide */
public static final int PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK = 0;
/** @hide */
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index a5586227cbb3..4a9e945e62a9 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -155,11 +155,4 @@ flag {
description: "Feature flag to add the privileged flag to the SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE permission"
bug: "380120712"
is_fixed_read_only: true
-}
-
-flag {
- name: "disable_adaptive_auth_counter_lock"
- namespace: "biometrics"
- description: "Flag to allow an adb secure setting to disable the adaptive auth lock"
- bug: "371057865"
-}
+} \ No newline at end of file
diff --git a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
index eea93b321e47..2e661b475dc9 100644
--- a/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
+++ b/core/java/android/service/settings/preferences/SettingsPreferenceValue.java
@@ -34,8 +34,7 @@ import java.lang.annotation.RetentionPolicy;
* This objects represents a value that can be used for a particular settings preference.
* <p>The data type for the value will correspond to {@link #getType}. For possible types, see
* constants below, such as {@link #TYPE_BOOLEAN} and {@link #TYPE_STRING}.
- * Depending on the type, the corresponding getter will contain its value. All other getters will
- * return default values (boolean returns false, String returns null) so they should not be used.
+ * Depending on the type, the corresponding getter will contain its value.
* <p>See documentation on the constants for which getter method should be used.
*/
@FlaggedApi(Flags.FLAG_SETTINGS_CATALYST)
@@ -43,12 +42,7 @@ public final class SettingsPreferenceValue implements Parcelable {
@Type
private final int mType;
- private final boolean mBooleanValue;
- private final int mIntValue;
- private final long mLongValue;
- private final double mDoubleValue;
- @Nullable
- private final String mStringValue;
+ private final @Nullable Object mValue;
/**
* Returns the type indicator for Preference value.
@@ -59,39 +53,39 @@ public final class SettingsPreferenceValue implements Parcelable {
}
/**
- * Returns the boolean value for Preference if type is {@link #TYPE_BOOLEAN}.
+ * Returns the boolean value for Preference, the type must be {@link #TYPE_BOOLEAN}.
*/
public boolean getBooleanValue() {
- return mBooleanValue;
+ return (boolean) mValue;
}
/**
- * Returns the int value for Preference if type is {@link #TYPE_INT}.
+ * Returns the int value for Preference, the type must be {@link #TYPE_INT}.
*/
public int getIntValue() {
- return mIntValue;
+ return (int) mValue;
}
/**
- * Returns the long value for Preference if type is {@link #TYPE_LONG}.
+ * Returns the long value for Preference, the type must be {@link #TYPE_LONG}.
*/
public long getLongValue() {
- return mLongValue;
+ return (long) mValue;
}
/**
- * Returns the double value for Preference if type is {@link #TYPE_DOUBLE}.
+ * Returns the double value for Preference, the type must be {@link #TYPE_DOUBLE}.
*/
public double getDoubleValue() {
- return mDoubleValue;
+ return (double) mValue;
}
/**
- * Returns the string value for Preference if type is {@link #TYPE_STRING}.
+ * Returns the string value for Preference, the type must be {@link #TYPE_STRING}.
*/
@Nullable
public String getStringValue() {
- return mStringValue;
+ return (String) mValue;
}
/** @hide */
@@ -115,34 +109,47 @@ public final class SettingsPreferenceValue implements Parcelable {
public static final int TYPE_STRING = 3;
/** Value is of type int. Access via {@link #getIntValue}. */
public static final int TYPE_INT = 4;
+ /** Max type value. */
+ private static final int MAX_TYPE_VALUE = TYPE_INT;
private SettingsPreferenceValue(@NonNull Builder builder) {
mType = builder.mType;
- mBooleanValue = builder.mBooleanValue;
- mLongValue = builder.mLongValue;
- mDoubleValue = builder.mDoubleValue;
- mStringValue = builder.mStringValue;
- mIntValue = builder.mIntValue;
+ mValue = builder.mValue;
}
private SettingsPreferenceValue(@NonNull Parcel in) {
mType = in.readInt();
- mBooleanValue = in.readBoolean();
- mLongValue = in.readLong();
- mDoubleValue = in.readDouble();
- mStringValue = in.readString8();
- mIntValue = in.readInt();
+ if (mType == TYPE_BOOLEAN) {
+ mValue = in.readBoolean();
+ } else if (mType == TYPE_LONG) {
+ mValue = in.readLong();
+ } else if (mType == TYPE_DOUBLE) {
+ mValue = in.readDouble();
+ } else if (mType == TYPE_STRING) {
+ mValue = in.readString();
+ } else if (mType == TYPE_INT) {
+ mValue = in.readInt();
+ } else {
+ // throw exception immediately, further read to Parcel may be invalid
+ throw new IllegalStateException("Unknown type: " + mType);
+ }
}
/** @hide */
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
- dest.writeBoolean(mBooleanValue);
- dest.writeLong(mLongValue);
- dest.writeDouble(mDoubleValue);
- dest.writeString8(mStringValue);
- dest.writeInt(mIntValue);
+ if (mType == TYPE_BOOLEAN) {
+ dest.writeBoolean(getBooleanValue());
+ } else if (mType == TYPE_LONG) {
+ dest.writeLong(getLongValue());
+ } else if (mType == TYPE_DOUBLE) {
+ dest.writeDouble(getDoubleValue());
+ } else if (mType == TYPE_STRING) {
+ dest.writeString(getStringValue());
+ } else if (mType == TYPE_INT) {
+ dest.writeInt(getIntValue());
+ }
}
/** @hide */
@@ -174,17 +181,16 @@ public final class SettingsPreferenceValue implements Parcelable {
public static final class Builder {
@Type
private final int mType;
- private boolean mBooleanValue;
- private long mLongValue;
- private double mDoubleValue;
- private String mStringValue;
- private int mIntValue;
+ private @Nullable Object mValue;
/**
* Create Builder instance.
* @param type type indicator for preference value
*/
public Builder(@Type int type) {
+ if (type < 0 || type > MAX_TYPE_VALUE) {
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
mType = type;
}
@@ -194,7 +200,8 @@ public final class SettingsPreferenceValue implements Parcelable {
@SuppressLint("MissingGetterMatchingBuilder")
@NonNull
public Builder setBooleanValue(boolean booleanValue) {
- mBooleanValue = booleanValue;
+ checkType(TYPE_BOOLEAN);
+ mValue = booleanValue;
return this;
}
@@ -203,7 +210,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setIntValue(int intValue) {
- mIntValue = intValue;
+ checkType(TYPE_INT);
+ mValue = intValue;
return this;
}
@@ -212,7 +220,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setLongValue(long longValue) {
- mLongValue = longValue;
+ checkType(TYPE_LONG);
+ mValue = longValue;
return this;
}
@@ -221,7 +230,8 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setDoubleValue(double doubleValue) {
- mDoubleValue = doubleValue;
+ checkType(TYPE_DOUBLE);
+ mValue = doubleValue;
return this;
}
@@ -230,10 +240,17 @@ public final class SettingsPreferenceValue implements Parcelable {
*/
@NonNull
public Builder setStringValue(@Nullable String stringValue) {
- mStringValue = stringValue;
+ checkType(TYPE_STRING);
+ mValue = stringValue;
return this;
}
+ private void checkType(int type) {
+ if (mType != type) {
+ throw new IllegalArgumentException("Type is: " + mType);
+ }
+ }
+
/**
* Constructs an immutable {@link SettingsPreferenceValue} object.
*/
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3c53506990d1..323d83b92143 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1066,6 +1066,12 @@ public abstract class Layout {
var hasBgColorChanged = newBackground != bgPaint.getColor();
if (lineNum != mLastLineNum || hasBgColorChanged) {
+ // Skip processing if the character is a space or a tap to avoid
+ // rendering an abrupt, empty rectangle.
+ if (Character.isWhitespace(mText.charAt(index))) {
+ return;
+ }
+
// Draw what we have so far, then reset the rect and update its color
drawRect();
mLineBackground.set(left, top, right, bottom);
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 6f7660a0fb3d..9d0ea547e734 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -161,6 +161,16 @@ public final class TaskFragmentOperation implements Parcelable {
*/
public static final int OP_TYPE_SET_PINNED = 19;
+ /**
+ * Sets whether this TaskFragment can affect system UI flags such as the status bar. Default
+ * is {@code true}.
+ *
+ * This operation is only allowed for system organizers. See
+ * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+ * ITaskFragmentOrganizer, boolean)}
+ */
+ public static final int OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS = 20;
+
@IntDef(prefix = { "OP_TYPE_" }, value = {
OP_TYPE_UNKNOWN,
OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -183,6 +193,7 @@ public final class TaskFragmentOperation implements Parcelable {
OP_TYPE_SET_MOVE_TO_BOTTOM_IF_CLEAR_WHEN_LAUNCH,
OP_TYPE_SET_DECOR_SURFACE_BOOSTED,
OP_TYPE_SET_PINNED,
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface OperationType {}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7a1078f8718f..8b1fd6c50c99 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -432,3 +432,19 @@ flag {
bug: "384976265"
}
+flag {
+ name: "aod_transition"
+ namespace: "windowing_frontend"
+ description: "Support to show lock wallpaper in aod state"
+ bug: "361438779"
+}
+
+flag {
+ name: "check_disabled_snapshots_in_task_persister"
+ namespace: "windowing_frontend"
+ description: "Check for TaskSnapshots disabling in TaskSnapshotPersister."
+ bug: "387915176"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index d1adfc95461d..158b526cb1a0 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -257,8 +257,16 @@ public class Cuj {
*/
public static final int CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU = 120;
+ /** Track Launcher Overview Task Dismiss animation.
+ *
+ * <p>Tracking starts when the overview task is dismissed via
+ * {@link com.android.quickstep.views.RecentsView#dismissTask}. Tracking finishes when the
+ * animation to dismiss the overview task ends.
+ */
+ public static final int CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS = 121;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS;
/** @hide */
@IntDef({
@@ -370,7 +378,8 @@ public class Cuj {
CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE,
CUJ_DESKTOP_MODE_SNAP_RESIZE,
CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW,
- CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU
+ CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU,
+ CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -493,6 +502,7 @@ public class Cuj {
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_SNAP_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_SNAP_RESIZE;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_UNMAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_UNMAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OVERVIEW_TASK_DISMISS;
}
private Cuj() {
@@ -729,6 +739,8 @@ public class Cuj {
return "DESKTOP_MODE_UNMAXIMIZE_WINDOW";
case CUJ_DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU:
return "DESKTOP_MODE_ENTER_FROM_OVERVIEW_MENU";
+ case CUJ_LAUNCHER_OVERVIEW_TASK_DISMISS:
+ return "LAUNCHER_OVERVIEW_TASK_DISMISS";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java
index 1709ca78af4b..f6de3459a1f5 100644
--- a/core/java/com/android/internal/os/RuntimeInit.java
+++ b/core/java/com/android/internal/os/RuntimeInit.java
@@ -174,7 +174,9 @@ public class RuntimeInit {
// System process is dead; ignore
} else {
try {
- Clog_e(TAG, "Error reporting crash", t2);
+ // Log original crash and then log the error reporting exception.
+ Clog_e(TAG, "Couldn't report crash. Here's the crash:", e);
+ Clog_e(TAG, "Error reporting crash. Here's the error:", t2);
} catch (Throwable t3) {
// Even Clog_e() fails! Oh well.
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ed021b64f7a0..445080215017 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -8804,22 +8804,6 @@
<permission android:name="android.permission.RESERVED_FOR_TESTING_SIGNATURE"
android:protectionLevel="signature"/>
- <!--
- This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component
- state of a non-exported component has been changed.
- <p>Not for use by third-party applications. </p>
- <p>Protection level: internal
- @hide
- -->
- <permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:protectionLevel="internal"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
- <uses-permission
- android:name="android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED"
- android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/>
-
<!-- @SystemApi
@FlaggedApi("android.media.tv.flags.kids_mode_tvdb_sharing")
This permission is required when accessing information related to
@@ -9272,6 +9256,11 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
+ <service android:name="com.android.server.ZramMaintenance"
+ android:exported="false"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.ZramWriteback"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" >
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e14cffd72b0c..416e0aeb776c 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -3156,6 +3156,16 @@
with admin privileges and admin privileges can be granted/revoked from existing users. -->
<bool name="config_enableMultipleAdmins">false</bool>
+ <!-- Whether to start stopped users before their scheduled alarms. If set to true, users will be
+ started in background before the alarm time so that it can go off. If false, alarms of
+ stopped users will not go off and users will remain stopped. -->
+ <bool name="config_allowAlarmsOnStoppedUsers">true</bool>
+
+ <!-- Whether notification is shown to foreground user when alarm/timer goes off on background
+ user. If set to true, foreground user will receive a notification with ability to mute
+ sound or switch user. If false, system notification will not be shown. -->
+ <bool name="config_showNotificationForBackgroundUserAlarms">true</bool>
+
<!-- Whether there is a communal profile which should always be running.
Only relevant for Headless System User Mode (HSUM) devices. -->
<bool name="config_omnipresentCommunalUser">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 68008e57094d..84d51f0b8ad8 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -366,6 +366,8 @@
<java-symbol type="bool" name="config_canSwitchToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_enableMultiUserUI"/>
<java-symbol type="bool" name="config_enableMultipleAdmins"/>
+ <java-symbol type="bool" name="config_allowAlarmsOnStoppedUsers"/>
+ <java-symbol type="bool" name="config_showNotificationForBackgroundUserAlarms"/>
<java-symbol type="bool" name="config_bootToHeadlessSystemUser"/>
<java-symbol type="bool" name="config_omnipresentCommunalUser"/>
<java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index c7d85d4b9b76..9e78af57b470 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -1024,6 +1024,55 @@ public class LayoutTest {
expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn);
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testWhitespaceText_DrawsBackgroundsWithAdjacentLetters() {
+ mTextPaint.setColor(Color.BLACK);
+ SpannableString spannedText = new SpannableString("Test\tTap and Space");
+
+ // Set the entire text to white initially
+ spannedText.setSpan(
+ new ForegroundColorSpan(Color.WHITE),
+ /* start= */ 0,
+ /* end= */ spannedText.length(),
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+
+ // Find the whitespace character and set its color to black
+ for (int i = 0; i < spannedText.length(); i++) {
+ if (Character.isWhitespace(spannedText.charAt(i))) {
+ spannedText.setSpan(
+ new ForegroundColorSpan(Color.BLACK),
+ i,
+ i + 1,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+ }
+ }
+
+ Layout layout = new StaticLayout(spannedText, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ MockCanvas c = new MockCanvas(/* width= */ 256, /* height= */ 256);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ for (int i = 0; i < drawCommands.size(); i++) {
+ MockCanvas.DrawCommand drawCommand = drawCommands.get(i);
+ if (drawCommand.rect != null) {
+ expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK);
+ }
+ }
+ }
+
private int removeAlpha(int color) {
return Color.rgb(
Color.red(color),
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 3403bbfa2384..3b4014867ef7 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -102,7 +102,7 @@ Changes to the whitelist during system updates can result in installing addition
to pre-existing users, but cannot uninstall pre-existing system packages from pre-existing users.
-->
<config>
- <!-- Bluetooth (com.android.btservices apex) - visible on the sharesheet -->
+ <!-- Bluetooth (com.android.bt apex) - visible on the sharesheet -->
<install-in-user-type package="com.android.bluetooth">
<install-in user-type="SYSTEM" />
<install-in user-type="FULL" />
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index e6c652c14c71..5e93f8db9388 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -20,6 +20,7 @@ import static android.security.keystore2.AndroidKeyStoreCipherSpiBase.DEFAULT_MG
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.Context;
import android.hardware.security.keymint.EcCurve;
import android.hardware.security.keymint.KeyParameter;
@@ -732,6 +733,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
}
}
+ @RequiresPermission(value = android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
+ conditional = true)
private void addAttestationParameters(@NonNull List<KeyParameter> params)
throws ProviderException, IllegalArgumentException, DeviceIdAttestationException {
byte[] challenge = mSpec.getAttestationChallenge();
@@ -824,7 +827,13 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato
break;
}
case AttestationUtils.ID_TYPE_MEID: {
- final String meid = telephonyService.getMeid(0);
+ String meid;
+ try {
+ meid = telephonyService.getMeid(0);
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Unable to retrieve MEID", e);
+ meid = null;
+ }
if (meid == null) {
throw new DeviceIdAttestationException("Unable to retrieve MEID");
}
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 957d1b835ec2..bcb6c4f555f7 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -170,9 +170,9 @@ android_library {
"res",
],
static_libs: [
+ "//frameworks/base/packages/SystemUI/aconfig:com_android_systemui_flags_lib",
"//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
"//frameworks/libs/systemui:iconloader_base",
- "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
"PlatformAnimationLib",
"WindowManager-Shell-lite-proto",
"WindowManager-Shell-proto",
diff --git a/libs/WindowManager/Shell/aconfig/Android.bp b/libs/WindowManager/Shell/aconfig/Android.bp
index 7f8f57b172ff..f8da7fa86cff 100644
--- a/libs/WindowManager/Shell/aconfig/Android.bp
+++ b/libs/WindowManager/Shell/aconfig/Android.bp
@@ -4,6 +4,7 @@ aconfig_declarations {
container: "system",
srcs: [
"multitasking.aconfig",
+ "automotive.aconfig",
],
}
diff --git a/libs/WindowManager/Shell/aconfig/automotive.aconfig b/libs/WindowManager/Shell/aconfig/automotive.aconfig
new file mode 100644
index 000000000000..2f25aa460ec1
--- /dev/null
+++ b/libs/WindowManager/Shell/aconfig/automotive.aconfig
@@ -0,0 +1,11 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
+package: "com.android.wm.shell"
+container: "system"
+
+flag {
+ name: "enable_auto_task_stack_controller"
+ namespace: "multitasking"
+ description: "Enables auto task stack controller to manage task stacks on automotive"
+ bug: "384082238"
+}
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 73e7223a3aea..b10b099c970b 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -13,13 +13,6 @@ flag {
}
flag {
- name: "enable_split_contextual"
- namespace: "multitasking"
- description: "Enables invoking split contextually"
- bug: "276361926"
-}
-
-flag {
name: "enable_taskbar_navbar_unification"
namespace: "multitasking"
description: "Enables taskbar / navbar unification"
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
index c45f6903c2e1..117ede0d0ac8 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt
@@ -290,11 +290,10 @@ class BubbleBarAnimationHelperTest {
assertThat(semaphore.tryAcquire(5, TimeUnit.SECONDS)).isTrue()
- val bbevBottom = bbev.contentBottomOnScreen + bubblePositioner.insets.top
activityScenario.onActivity {
// notify that the IME top coordinate is greater than the bottom of the expanded view.
// there's no overlap so it should not be clipped.
- animationHelper.onImeTopChanged(bbevBottom * 2)
+ animationHelper.onImeTopChanged(bbev.contentBottomOnScreen * 2)
}
val outline = Outline()
bbev.outlineProvider.getOutline(bbev, outline)
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
index 7347fbad5f5d..fc8b29912955 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_manage_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
index f0e1871168dd..1616707954f5 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -38,7 +38,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/bubble_popup_text_margin"
android:maxWidth="@dimen/bubble_popup_content_max_width"
- android:maxLines="1"
+ android:maxLines="2"
android:ellipsize="end"
android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
android:textColor="@androidprv:color/materialColorOnSurface"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
index 82ef00e46e8c..10023c9dba40 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/appzoomout/AppZoomOutController.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.appzoomout;
import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.systemui.Flags.spatialModelAppPushback;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -93,7 +94,9 @@ public class AppZoomOutController implements RemoteCallable<AppZoomOutController
mDisplayAreaOrganizer = displayAreaOrganizer;
mMainExecutor = mainExecutor;
- shellInit.addInitCallback(this::onInit, this);
+ if (spatialModelAppPushback()) {
+ shellInit.addInitCallback(this::onInit, this);
+ }
}
private void onInit() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
index f8f284238a98..8171312762ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -33,7 +33,7 @@ import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
import android.window.TransitionRequestInfo
import android.window.WindowContainerTransaction
-import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.Flags.enableAutoTaskStackController
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.ShellExecutor
@@ -66,7 +66,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
init {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
throw IllegalStateException("Failed to initialize" +
"AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
} else {
@@ -220,7 +220,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
displayId: Int,
listener: RootTaskStackListener
) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to create root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -236,7 +236,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to set default root task stack as the " +
"auto_task_stack_windowing TS flag is disabled."
@@ -280,7 +280,7 @@ class AutoTaskStackControllerImpl @Inject constructor(
}
override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
- if (!autoTaskStackWindowing()) {
+ if (!enableAutoTaskStackController()) {
Slog.e(
TAG, "Failed to start transaction as the " +
"auto_task_stack_windowing TS flag is disabled."
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
index 862906a11424..62995319db80 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt
@@ -136,23 +136,15 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl
// Update bitmap
val fg = InsetDrawable(overflowBtn?.iconDrawable, overflowIconInset)
- bitmap =
- iconFactory
- .createBadgedIconBitmap(AdaptiveIconDrawable(ColorDrawable(colorAccent), fg))
- .icon
+ val drawable = AdaptiveIconDrawable(ColorDrawable(colorAccent), fg)
+ bitmap = iconFactory.createBadgedIconBitmap(drawable).icon
// Update dot path
dotPath =
PathParser.createPathFromPathData(
res.getString(com.android.internal.R.string.config_icon_mask)
)
- val scale =
- iconFactory.normalizer.getScale(
- iconView!!.iconDrawable,
- null /* outBounds */,
- null /* path */,
- null /* outMaskShape */
- )
+ val scale = iconFactory.normalizer.getScale(iconView!!.iconDrawable)
val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
val matrix = Matrix()
matrix.setScale(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
index e073b02dc630..ac5b9c9866ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java
@@ -674,9 +674,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
if (mTaskView != null) {
mTaskView.getBoundsOnScreen(mTempBounds);
}
- // return the bottom of the content rect, adjusted for insets so the result is in screen
- // coordinate
- return mTempBounds.bottom + mPositioner.getInsets().top;
+ return mTempBounds.bottom;
}
/** Update the amount by which to clip the expanded view at the bottom. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index c27ef295db51..c9136b4ad18d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -929,8 +929,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
for (int taskId : taskIds) {
ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
if (task != null) {
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
- .setBounds(task.token, null);
+ wct.setWindowingMode(task.getToken(), WINDOWING_MODE_UNDEFINED)
+ .setBounds(task.getToken(), null);
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 81f444ba2af3..c5994f83429a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -633,7 +633,7 @@ public class SplashscreenContentDrawer {
private class ShapeIconFactory extends BaseIconFactory {
protected ShapeIconFactory(Context context, int fillResIconDpi, int iconBitmapSize) {
- super(context, fillResIconDpi, iconBitmapSize, true /* shapeDetection */);
+ super(context, fillResIconDpi, iconBitmapSize);
}
}
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 9fbda46bd2b7..67dae283345a 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
@@ -1969,7 +1969,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
Supplier<SurfaceControl.Transaction> transactionFactory,
Handler handler) {
final TaskPositioner taskPositioner = DesktopModeStatus.isVeiledResizeEnabled()
- ? new VeiledResizeTaskPositioner(
+ // TODO(b/383632995): Update when the flag is launched.
+ ? (Flags.enableConnectedDisplaysWindowDrag()
+ ? new MultiDisplayVeiledResizeTaskPositioner(
taskOrganizer,
windowDecoration,
displayController,
@@ -1977,6 +1979,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
transitions,
interactionJankMonitor,
handler)
+ : new VeiledResizeTaskPositioner(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ transitions,
+ interactionJankMonitor,
+ handler))
: new FluidResizeTaskPositioner(
taskOrganizer,
transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
new file mode 100644
index 000000000000..8dc921c986ce
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositioner.kt
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.graphics.PointF
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.view.Choreographer
+import android.view.Surface
+import android.view.SurfaceControl
+import android.view.WindowManager
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import java.util.concurrent.TimeUnit
+import java.util.function.Supplier
+
+/**
+ * A task positioner that also takes into account resizing a
+ * [com.android.wm.shell.windowdecor.ResizeVeil] and dragging move across multiple displays.
+ * - If the drag is resizing the task, we resize the veil instead.
+ * - If the drag is repositioning, we consider multi-display topology if needed, and update in the
+ * typical manner.
+ */
+class MultiDisplayVeiledResizeTaskPositioner(
+ private val taskOrganizer: ShellTaskOrganizer,
+ private val desktopWindowDecoration: DesktopModeWindowDecoration,
+ private val displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
+ private val transitions: Transitions,
+ private val interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread private val handler: Handler,
+) : TaskPositioner, Transitions.TransitionHandler {
+ private val dragEventListeners =
+ mutableListOf<DragPositioningCallbackUtility.DragEventListener>()
+ private val stableBounds = Rect()
+ private val taskBoundsAtDragStart = Rect()
+ private val repositionStartPoint = PointF()
+ private val repositionTaskBounds = Rect()
+ private val isResizing: Boolean
+ get() =
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_TOP) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_BOTTOM) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_LEFT) != 0 ||
+ (ctrlType and DragPositioningCallback.CTRL_TYPE_RIGHT) != 0
+
+ @DragPositioningCallback.CtrlType private var ctrlType = 0
+ private var isResizingOrAnimatingResize = false
+ @Surface.Rotation private var rotation = 0
+
+ constructor(
+ taskOrganizer: ShellTaskOrganizer,
+ windowDecoration: DesktopModeWindowDecoration,
+ displayController: DisplayController,
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener,
+ transitions: Transitions,
+ interactionJankMonitor: InteractionJankMonitor,
+ @ShellMainThread handler: Handler,
+ ) : this(
+ taskOrganizer,
+ windowDecoration,
+ displayController,
+ dragEventListener,
+ Supplier<SurfaceControl.Transaction> { SurfaceControl.Transaction() },
+ transitions,
+ interactionJankMonitor,
+ handler,
+ )
+
+ init {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun onDragPositioningStart(ctrlType: Int, displayId: Int, x: Float, y: Float): Rect {
+ this.ctrlType = ctrlType
+ taskBoundsAtDragStart.set(
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.bounds
+ )
+ repositionStartPoint[x] = y
+ if (isResizing) {
+ // Capture CUJ for re-sizing window in DW mode.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ )
+ if (!desktopWindowDecoration.mHasGlobalFocus) {
+ val wct = WindowContainerTransaction()
+ wct.reorder(
+ desktopWindowDecoration.mTaskInfo.token,
+ /* onTop= */ true,
+ /* includingParents= */ true,
+ )
+ taskOrganizer.applyTransaction(wct)
+ }
+ }
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragStart(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ repositionTaskBounds.set(taskBoundsAtDragStart)
+ val rotation =
+ desktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.displayRotation
+ if (stableBounds.isEmpty || this.rotation != rotation) {
+ this.rotation = rotation
+ displayController
+ .getDisplayLayout(desktopWindowDecoration.mDisplay.displayId)!!
+ .getStableBounds(stableBounds)
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningMove(displayId: Int, x: Float, y: Float): Rect {
+ check(Looper.myLooper() == handler.looper) {
+ "This method must run on the shell main thread."
+ }
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (
+ isResizing &&
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ ) {
+ if (!isResizingOrAnimatingResize) {
+ for (dragEventListener in dragEventListeners) {
+ dragEventListener.onDragMove(desktopWindowDecoration.mTaskInfo.taskId)
+ }
+ desktopWindowDecoration.showResizeVeil(repositionTaskBounds)
+ isResizingOrAnimatingResize = true
+ } else {
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ }
+ } else if (ctrlType == DragPositioningCallback.CTRL_TYPE_UNDEFINED) {
+ // Begin window drag CUJ instrumentation only when drag position moves.
+ interactionJankMonitor.begin(
+ createLongTimeoutJankConfigBuilder(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ )
+ val t = transactionSupplier.get()
+ DragPositioningCallbackUtility.setPositionOnDrag(
+ desktopWindowDecoration,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ t,
+ x,
+ y,
+ )
+ t.setFrameTimeline(Choreographer.getInstance().vsyncId)
+ t.apply()
+ }
+ return Rect(repositionTaskBounds)
+ }
+
+ override fun onDragPositioningEnd(displayId: Int, x: Float, y: Float): Rect {
+ val delta = DragPositioningCallbackUtility.calculateDelta(x, y, repositionStartPoint)
+ if (isResizing) {
+ if (taskBoundsAtDragStart != repositionTaskBounds) {
+ DragPositioningCallbackUtility.changeBounds(
+ ctrlType,
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ stableBounds,
+ delta,
+ displayController,
+ desktopWindowDecoration,
+ )
+ desktopWindowDecoration.updateResizeVeil(repositionTaskBounds)
+ val wct = WindowContainerTransaction()
+ wct.setBounds(desktopWindowDecoration.mTaskInfo.token, repositionTaskBounds)
+ transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, this)
+ } else {
+ // If bounds haven't changed, perform necessary veil reset here as startAnimation
+ // won't be called.
+ resetVeilIfVisible()
+ }
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_RESIZE_WINDOW)
+ } else {
+ DragPositioningCallbackUtility.updateTaskBounds(
+ repositionTaskBounds,
+ taskBoundsAtDragStart,
+ repositionStartPoint,
+ x,
+ y,
+ )
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ }
+
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ taskBoundsAtDragStart.setEmpty()
+ repositionStartPoint[0f] = 0f
+ return Rect(repositionTaskBounds)
+ }
+
+ private fun resetVeilIfVisible() {
+ if (isResizingOrAnimatingResize) {
+ desktopWindowDecoration.hideResizeVeil()
+ isResizingOrAnimatingResize = false
+ }
+ }
+
+ private fun createLongTimeoutJankConfigBuilder(@Cuj.CujType cujType: Int) =
+ InteractionJankMonitor.Configuration.Builder.withSurface(
+ cujType,
+ desktopWindowDecoration.mContext,
+ desktopWindowDecoration.mTaskSurface,
+ handler,
+ )
+ .setTimeout(LONG_CUJ_TIMEOUT_MS)
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: Transitions.TransitionFinishCallback,
+ ): Boolean {
+ for (change in info.changes) {
+ val sc = change.leash
+ val endBounds = change.endAbsBounds
+ val endPosition = change.endRelOffset
+ startTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ finishTransaction
+ .setWindowCrop(sc, endBounds.width(), endBounds.height())
+ .setPosition(sc, endPosition.x.toFloat(), endPosition.y.toFloat())
+ }
+
+ startTransaction.apply()
+ resetVeilIfVisible()
+ ctrlType = DragPositioningCallback.CTRL_TYPE_UNDEFINED
+ finishCallback.onTransitionFinished(null /* wct */)
+ isResizingOrAnimatingResize = false
+ interactionJankMonitor.end(Cuj.CUJ_DESKTOP_MODE_DRAG_WINDOW)
+ return true
+ }
+
+ /**
+ * We should never reach this as this handler's transitions are only started from shell
+ * explicitly.
+ */
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo,
+ ): WindowContainerTransaction? {
+ return null
+ }
+
+ override fun isResizingOrAnimating() = isResizingOrAnimatingResize
+
+ override fun addDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.add(dragEventListener)
+ }
+
+ override fun removeDragEventListener(
+ dragEventListener: DragPositioningCallbackUtility.DragEventListener
+ ) {
+ dragEventListeners.remove(dragEventListener)
+ }
+
+ companion object {
+ // Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
+ // timing out in the middle of a resize or drag action.
+ private val LONG_CUJ_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(/* duration= */ 10L)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index e011cc08903b..d2c79d76e6c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -53,7 +53,12 @@ import java.util.function.Supplier;
* {@link com.android.wm.shell.windowdecor.ResizeVeil}.
* If the drag is resizing the task, we resize the veil instead.
* If the drag is repositioning, we update in the typical manner.
+ * <p>
+ * @deprecated This class will be replaced by
+ * {@link com.android.wm.shell.windowdecor.MultiDisplayVeiledResizeTaskPositioner}.
+ * TODO(b/383632995): Remove this class after MultiDisplayVeiledResizeTaskPositioner is launched.
*/
+@Deprecated
public class VeiledResizeTaskPositioner implements TaskPositioner, Transitions.TransitionHandler {
// Timeout used for resize and drag CUJs, this is longer than the default timeout to avoid
// timing out in the middle of a resize or drag action.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 2d0ea5fdc884..7a88ace3f85f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_INDEX_UNDEFINED;
@@ -32,6 +33,8 @@ import static com.google.common.truth.Truth.assertThat;
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.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.atLeastOnce;
@@ -50,9 +53,11 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Looper;
import android.view.SurfaceControl;
import android.window.RemoteTransition;
+import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.test.annotation.UiThreadTest;
@@ -84,6 +89,7 @@ import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -425,6 +431,30 @@ public class StageCoordinatorTests extends ShellTestCase {
.startFullscreenTransition(any(), any());
}
+
+ @Test
+ public void startTask_ensureWindowingModeCleared() {
+ SplitScreenTransitions splitScreenTransitions =
+ spy(mStageCoordinator.getSplitTransitions());
+ mStageCoordinator.setSplitTransitions(splitScreenTransitions);
+ ArgumentCaptor<WindowContainerTransaction> wctCaptor =
+ ArgumentCaptor.forClass(WindowContainerTransaction.class);
+ int taskId = 18;
+ IBinder binder = mock(IBinder.class);
+ ActivityManager.RunningTaskInfo rti = mock(ActivityManager.RunningTaskInfo.class);
+ WindowContainerToken mockToken = mock(WindowContainerToken.class);
+ when(mockToken.asBinder()).thenReturn(binder);
+ when(rti.getToken()).thenReturn(mockToken);
+ when(mTaskOrganizer.getRunningTaskInfo(taskId)).thenReturn(rti);
+ mStageCoordinator.startTask(taskId, SPLIT_POSITION_TOP_OR_LEFT, null /*options*/,
+ null, SPLIT_INDEX_UNDEFINED);
+ verify(splitScreenTransitions).startEnterTransition(anyInt(),
+ wctCaptor.capture(), any(), any(), anyInt(), anyBoolean());
+
+ int windowingMode = wctCaptor.getValue().getChanges().get(binder).getWindowingMode();
+ assertEquals(windowingMode, WINDOWING_MODE_UNDEFINED);
+ }
+
private Transitions createTestTransitions() {
ShellInit shellInit = new ShellInit(mMainExecutor);
final Transitions t = new Transitions(mContext, shellInit, mock(ShellController.class),
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
new file mode 100644
index 000000000000..f179cac32244
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/MultiDisplayVeiledResizeTaskPositionerTest.kt
@@ -0,0 +1,611 @@
+/*
+ * 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.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Point
+import android.graphics.Rect
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.Surface.ROTATION_0
+import android.view.Surface.ROTATION_270
+import android.view.Surface.ROTATION_90
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.WindowContainerToken
+import androidx.test.filters.SmallTest
+import androidx.test.internal.runner.junit4.statement.UiThreadStatement.runOnUiThread
+import com.android.internal.jank.InteractionJankMonitor
+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.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
+import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import java.util.function.Supplier
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [MultiDisplayVeiledResizeTaskPositioner].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:MultiDisplayVeiledResizeTaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiDisplayVeiledResizeTaskPositionerTest : ShellTestCase() {
+
+ @Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock
+ private lateinit var mockDragEventListener: DragPositioningCallbackUtility.DragEventListener
+
+ @Mock private lateinit var taskToken: WindowContainerToken
+ @Mock private lateinit var taskBinder: IBinder
+
+ @Mock private lateinit var mockDisplayController: DisplayController
+ @Mock private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock private lateinit var mockDisplay: Display
+ @Mock private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
+ @Mock private lateinit var mockTransaction: SurfaceControl.Transaction
+ @Mock private lateinit var mockTransitionBinder: IBinder
+ @Mock private lateinit var mockTransitionInfo: TransitionInfo
+ @Mock private lateinit var mockFinishCallback: TransitionFinishCallback
+ @Mock private lateinit var mockTransitions: Transitions
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockResources: Resources
+ @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
+ private val mainHandler = Handler(Looper.getMainLooper())
+
+ private lateinit var taskPositioner: MultiDisplayVeiledResizeTaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ mockDesktopWindowDecoration.mDecorWindowContext = mockContext
+ whenever(mockContext.getResources()).thenReturn(mockResources)
+ whenever(taskToken.asBinder()).thenReturn(taskBinder)
+ whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ whenever(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ if (
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_90 ||
+ mockDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration
+ .displayRotation == ROTATION_270
+ ) {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_LANDSCAPE)
+ } else {
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS_PORTRAIT)
+ }
+ }
+ `when`(mockTransactionFactory.get()).thenReturn(mockTransaction)
+ mockDesktopWindowDecoration.mTaskInfo =
+ ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.setBounds(STARTING_BOUNDS)
+ configuration.windowConfiguration.displayRotation = ROTATION_90
+ isResizeable = true
+ }
+ `when`(mockDesktopWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+
+ taskPositioner =
+ MultiDisplayVeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragEventListener,
+ mockTransactionFactory,
+ mockTransitions,
+ mockInteractionJankMonitor,
+ mainHandler,
+ )
+ }
+
+ @Test
+ fun testDragResize_noMove_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ }
+
+ @Test
+ fun testDragResize_movesTask_doesNotShowResizeVeil() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 60,
+ STARTING_BOUNDS.top.toFloat() + 100,
+ )
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.left += 60
+ rectAfterMove.right += 60
+ rectAfterMove.top += 100
+ rectAfterMove.bottom += 100
+ verify(mockTransaction)
+ .setPosition(any(), eq(rectAfterMove.left.toFloat()), eq(rectAfterMove.top.toFloat()))
+
+ val endBounds =
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 70,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(STARTING_BOUNDS)
+ rectAfterEnd.left += 70
+ rectAfterEnd.right += 70
+ rectAfterEnd.top += 20
+ rectAfterEnd.bottom += 20
+
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(any())
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ Assert.assertEquals(rectAfterEnd, endBounds)
+ }
+
+ @Test
+ fun testDragResize_resize_boundsUpdateOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterMove
+ }
+ }
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.right.toFloat() + 20,
+ STARTING_BOUNDS.top.toFloat() + 20,
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.right += 10
+ rectAfterEnd.top += 10
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterEnd
+ }
+ },
+ eq(taskPositioner),
+ )
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ DISPLAY_ID,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10,
+ )
+
+ verify(mockTransitions, never())
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == STARTING_BOUNDS
+ }
+ },
+ eq(taskPositioner),
+ )
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() = runOnUiThread {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ val newX = STARTING_BOUNDS.left.toFloat() + 5
+ val newY = DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT.toFloat() - 1
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, newX, newY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, newX, newY)
+
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0)
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskReorderedToTopWhenNotFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is reordered to top
+ verify(mockShellTaskOrganizer)
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_resize_resizingTaskNotReorderedToTopWhenFocused() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = true
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT, // Resize right
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_draggedTaskNotReorderedToTop() = runOnUiThread {
+ mockDesktopWindowDecoration.mHasGlobalFocus = false
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED, // drag
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // Verify task is not reordered to top since task is already brought to top before dragging
+ // begins
+ verify(mockShellTaskOrganizer, never())
+ .applyTransaction(
+ argThat { wct ->
+ return@argThat wct.hierarchyOps.any { hierarchyOps ->
+ hierarchyOps.container == taskBinder && hierarchyOps.toTop
+ }
+ }
+ )
+ }
+
+ @Test
+ fun testDragResize_drag_updatesStableBoundsOnRotate() = runOnUiThread {
+ // Test landscape stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ val rectAfterDrag = Rect(STARTING_BOUNDS)
+ rectAfterDrag.right += 2000
+ rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
+ // First drag; we should fetch stable bounds.
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Drag back to starting bounds.
+ performDrag(
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+
+ // Display did not rotate; we should use previous stable bounds
+ verify(mockDisplayLayout, times(1)).getStableBounds(any())
+
+ // Rotate the screen to portrait
+ mockDesktopWindowDecoration.mTaskInfo.apply {
+ configuration.windowConfiguration.displayRotation = ROTATION_0
+ }
+ // Test portrait stable bounds
+ performDrag(
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.bottom.toFloat(),
+ STARTING_BOUNDS.right.toFloat() + 2000,
+ STARTING_BOUNDS.bottom.toFloat() + 2000,
+ CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+ )
+ rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+ rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
+
+ verify(mockTransitions)
+ .startTransition(
+ eq(TRANSIT_CHANGE),
+ argThat { wct ->
+ return@argThat wct.changes.any { (token, change) ->
+ token == taskBinder &&
+ (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) !=
+ 0 &&
+ change.configuration.windowConfiguration.bounds == rectAfterDrag
+ }
+ },
+ eq(taskPositioner),
+ )
+ // Display has rotated; we expect a new stable bounds.
+ verify(mockDisplayLayout, times(2)).getStableBounds(any())
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeSet() = runOnUiThread {
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ taskPositioner.onDragPositioningMove(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ )
+
+ // isResizingOrAnimating should be set to true after move during a resize
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ verify(mockDragEventListener, times(1)).onDragMove(eq(TASK_ID))
+
+ taskPositioner.onDragPositioningEnd(
+ DISPLAY_ID,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ )
+
+ // isResizingOrAnimating should be not be set till false until after transition animation
+ Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() = runOnUiThread {
+ performDrag(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat(),
+ STARTING_BOUNDS.left.toFloat() - 20,
+ STARTING_BOUNDS.top.toFloat() - 20,
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ )
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ mockTransaction,
+ mockTransaction,
+ mockFinishCallback,
+ )
+
+ // isResizingOrAnimating should be set to false until after transition successfully consumed
+ Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+ }
+
+ @Test
+ fun testStartAnimation_useEndRelOffset() = runOnUiThread {
+ val changeMock = mock(TransitionInfo.Change::class.java)
+ val startTransaction = mock(Transaction::class.java)
+ val finishTransaction = mock(Transaction::class.java)
+ val point = Point(10, 20)
+ val bounds = Rect(1, 2, 3, 4)
+ `when`(changeMock.leash).thenReturn(mock(SurfaceControl::class.java))
+ `when`(changeMock.endRelOffset).thenReturn(point)
+ `when`(changeMock.endAbsBounds).thenReturn(bounds)
+ `when`(mockTransitionInfo.changes).thenReturn(listOf(changeMock))
+ `when`(startTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(startTransaction)
+ `when`(finishTransaction.setWindowCrop(any(), eq(bounds.width()), eq(bounds.height())))
+ .thenReturn(finishTransaction)
+
+ taskPositioner.startAnimation(
+ mockTransitionBinder,
+ mockTransitionInfo,
+ startTransaction,
+ finishTransaction,
+ mockFinishCallback,
+ )
+
+ verify(startTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(finishTransaction).setPosition(any(), eq(point.x.toFloat()), eq(point.y.toFloat()))
+ verify(changeMock).endRelOffset
+ }
+
+ private fun performDrag(startX: Float, startY: Float, endX: Float, endY: Float, ctrlType: Int) {
+ taskPositioner.onDragPositioningStart(ctrlType, DISPLAY_ID, startX, startY)
+ taskPositioner.onDragPositioningMove(DISPLAY_ID, endX, endY)
+
+ taskPositioner.onDragPositioningEnd(DISPLAY_ID, endX, endY)
+ }
+
+ companion object {
+ private const val TASK_ID = 5
+ private const val MIN_WIDTH = 10
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
+ private const val NAVBAR_HEIGHT = 50
+ private const val CAPTION_HEIGHT = 50
+ private const val DISALLOWED_AREA_FOR_END_BOUNDS_HEIGHT = 10
+ private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600)
+ private val STARTING_BOUNDS = Rect(100, 100, 200, 200)
+ private val STABLE_BOUNDS_LANDSCAPE =
+ Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
+ )
+ private val STABLE_BOUNDS_PORTRAIT =
+ Rect(
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.left + CAPTION_HEIGHT,
+ DISPLAY_BOUNDS.bottom,
+ DISPLAY_BOUNDS.right - NAVBAR_HEIGHT,
+ )
+ private val VALID_DRAG_AREA =
+ Rect(
+ DISPLAY_BOUNDS.left - 100,
+ STABLE_BOUNDS_LANDSCAPE.top,
+ DISPLAY_BOUNDS.right - 100,
+ DISPLAY_BOUNDS.bottom - 100,
+ )
+ }
+}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index f42017dc835a..3104f9d42891 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -503,7 +503,10 @@ public abstract class MediaRoute2ProviderService extends Service {
String sessionId = sessionInfo.getId();
synchronized (mSessionLock) {
- if (mSessionInfos.containsKey(sessionId)) {
+ var mediaStreams = mOngoingMediaStreams.get(sessionId);
+ if (Flags.enableMirroringInMediaRouter2() && mediaStreams != null) {
+ mediaStreams.mSessionInfo = sessionInfo;
+ } else if (mSessionInfos.containsKey(sessionId)) {
mSessionInfos.put(sessionId, sessionInfo);
} else {
Log.w(TAG, "notifySessionUpdated: Ignoring unknown session info.");
@@ -836,6 +839,9 @@ public abstract class MediaRoute2ProviderService extends Service {
List<RoutingSessionInfo> sessions;
synchronized (mSessionLock) {
sessions = new ArrayList<>(mSessionInfos.values());
+ if (Flags.enableMirroringInMediaRouter2()) {
+ mOngoingMediaStreams.values().forEach(it -> sessions.add(it.mSessionInfo));
+ }
}
try {
@@ -888,7 +894,13 @@ public abstract class MediaRoute2ProviderService extends Service {
Log.w(TAG, description + ": Ignoring empty sessionId from system service.");
return false;
}
- if (getSessionInfo(sessionId) == null) {
+ boolean idMatchesSystemSession = false;
+ if (Flags.enableMirroringInMediaRouter2()) {
+ synchronized (mSessionLock) {
+ idMatchesSystemSession = mOngoingMediaStreams.containsKey(sessionId);
+ }
+ }
+ if (!idMatchesSystemSession && getSessionInfo(sessionId) == null) {
Log.w(TAG, description + ": Ignoring unknown session from system service. "
+ "sessionId=" + sessionId);
return false;
@@ -1079,8 +1091,8 @@ public abstract class MediaRoute2ProviderService extends Service {
*
* @hide
*/
- @GuardedBy("MediaRoute2ProviderService.this.mSessionLock")
@NonNull
+ // Access guarded by mSessionsLock, but it's not convenient to enforce through @GuardedBy.
private RoutingSessionInfo mSessionInfo;
// TODO: b/380431086: Add the video equivalent.
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 3451dfc559ee..28da55656177 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -120,3 +120,13 @@ flag {
description: "Collect physical address from HDMI-CEC messages in metrics"
bug: "376001043"
}
+
+flag {
+ name: "tif_extension_standardization_bugfix"
+ namespace: "tv_os"
+ description: "Bug fix flag for standardizing AIDL extension interface of TIS"
+ bug: "389779152"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+} \ No newline at end of file
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 2fe069af638a..bf330dab266c 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -701,6 +701,9 @@ void FilterClientCallbackImpl::getMediaEvent(const jobjectArray& arr, const int
// Protect mFilterClient from being set to null.
android::Mutex::Autolock autoLock(mLock);
+ if (mFilterClient == nullptr) {
+ return;
+ }
uint64_t avSharedMemSize = mFilterClient->getAvSharedHandleInfo().size;
if (mediaEvent.avMemory.fds.size() > 0 || mediaEvent.avDataId != 0 ||
(dataLength > 0 && (dataLength + offset) < avSharedMemSize)) {
@@ -868,10 +871,18 @@ void FilterClientCallbackImpl::getRestartEvent(const jobjectArray& arr, const in
void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
ALOGV("FilterClientCallbackImpl::onFilterEvent");
JNIEnv *env = AndroidRuntime::getJNIEnv();
+
ScopedLocalRef<jobjectArray> array(env);
if (!events.empty()) {
array.reset(env->NewObjectArray(events.size(), mEventClass, nullptr));
+ if (env->IsSameObject(array.get(), nullptr)) {
+ // It can happen when FilterClientCallbackImpl release the resource
+ // in another thread.
+ ALOGE("FilterClientCallbackImpl::onFilterEvent:"
+ "Unable to create object array of filter events. Ignoring callback.");
+ return;
+ }
}
for (int i = 0, arraySize = 0; i < events.size(); i++) {
@@ -1070,14 +1081,15 @@ FilterClientCallbackImpl::FilterClientCallbackImpl() {
FilterClientCallbackImpl::~FilterClientCallbackImpl() {
JNIEnv *env = AndroidRuntime::getJNIEnv();
- {
- android::Mutex::Autolock autoLock(mLock);
- if (mFilterObj != nullptr) {
- env->DeleteWeakGlobalRef(mFilterObj);
- mFilterObj = nullptr;
- }
- mFilterClient = nullptr;
+
+ android::Mutex::Autolock autoLock(mLock);
+
+ if (mFilterObj != nullptr) {
+ env->DeleteWeakGlobalRef(mFilterObj);
+ mFilterObj = nullptr;
}
+ mFilterClient = nullptr;
+
env->DeleteGlobalRef(mEventClass);
env->DeleteGlobalRef(mSectionEventClass);
env->DeleteGlobalRef(mMediaEventClass);
diff --git a/native/android/TEST_MAPPING b/native/android/TEST_MAPPING
index e40af595f248..70560a84b88e 100644
--- a/native/android/TEST_MAPPING
+++ b/native/android/TEST_MAPPING
@@ -24,22 +24,6 @@
{
"name": "NativeThermalUnitTestCases",
"file_patterns": ["thermal.cpp"]
- },
- {
- "file_patterns": ["system_health.cpp"],
- "name": "NativeSystemHealthUnitTestCases",
- "options": [
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- },
- {
- "file_patterns": ["system_health.cpp"],
- "name": "CtsSystemHealthTestCases",
- "options": [
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
}
]
}
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 4180710534c3..bc273c2d0833 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -102,8 +102,13 @@ cc_defaults {
static_libs: ["libarect"],
fuzz_config: {
cc: [
+ // Alphabetical order -- assign to skia-android-triage@google.com
+ "danieldilan@google.com",
"dichenzhang@google.com",
- "scroggo@google.com",
+ "fmalita@google.com",
+ "jreck@google.com",
+ "nscobie@google.com",
+ "skia-android-triage@google.com",
],
asan_options: [
"detect_odr_violation=1",
diff --git a/nfc-non-updatable/flags/flags.aconfig b/nfc-non-updatable/flags/flags.aconfig
index 6b14a1ed3990..54ded0cddffa 100644
--- a/nfc-non-updatable/flags/flags.aconfig
+++ b/nfc-non-updatable/flags/flags.aconfig
@@ -197,3 +197,11 @@ flag {
description: "Expose constructor for ApduServiceInfo"
bug: "380892385"
}
+
+flag {
+ name: "nfc_hce_latency_events"
+ is_exported: true
+ namespace: "wallet_integration"
+ description: "Enables tracking latency for HCE"
+ bug: "379849603"
+}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java b/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
deleted file mode 100644
index fdb0fc538fdf..000000000000
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ /dev/null
@@ -1,359 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.service.watchdog;
-
-import static android.os.Parcelable.Creator;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SdkConstant;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.app.Service;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.crashrecovery.flags.Flags;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.util.Log;
-
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
-
-/**
- * A service to provide packages supporting explicit health checks and route checks to these
- * packages on behalf of the package watchdog.
- *
- * <p>To extend this class, you must declare the service in your manifest file with the
- * {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
- * and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
- * your implementation must live in
- * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
- * For example:</p>
- * <pre>
- * &lt;service android:name=".FooExplicitHealthCheckService"
- * android:exported="true"
- * android:priority="100"
- * android:permission="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"&gt;
- * &lt;intent-filter&gt;
- * &lt;action android:name="android.service.watchdog.ExplicitHealthCheckService" /&gt;
- * &lt;/intent-filter&gt;
- * &lt;/service&gt;
- * </pre>
- * @hide
- */
-@SystemApi
-public abstract class ExplicitHealthCheckService extends Service {
-
- private static final String TAG = "ExplicitHealthCheckService";
-
- /**
- * {@link Bundle} key for a {@link List} of {@link PackageConfig} value.
- *
- * {@hide}
- */
- public static final String EXTRA_SUPPORTED_PACKAGES =
- "android.service.watchdog.extra.supported_packages";
-
- /**
- * {@link Bundle} key for a {@link List} of {@link String} value.
- *
- * {@hide}
- */
- public static final String EXTRA_REQUESTED_PACKAGES =
- "android.service.watchdog.extra.requested_packages";
-
- /**
- * {@link Bundle} key for a {@link String} value.
- */
- @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
- public static final String EXTRA_HEALTH_CHECK_PASSED_PACKAGE =
- "android.service.watchdog.extra.HEALTH_CHECK_PASSED_PACKAGE";
-
- /**
- * The Intent action that a service must respond to. Add it to the intent filter of the service
- * in its manifest.
- */
- @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
- public static final String SERVICE_INTERFACE =
- "android.service.watchdog.ExplicitHealthCheckService";
-
- /**
- * The permission that a service must require to ensure that only Android system can bind to it.
- * If this permission is not enforced in the AndroidManifest of the service, the system will
- * skip that service.
- */
- public static final String BIND_PERMISSION =
- "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
-
- private final ExplicitHealthCheckServiceWrapper mWrapper =
- new ExplicitHealthCheckServiceWrapper();
-
- /**
- * Called when the system requests an explicit health check for {@code packageName}.
- *
- * <p> When {@code packageName} passes the check, implementors should call
- * {@link #notifyHealthCheckPassed} to inform the system.
- *
- * <p> It could take many hours before a {@code packageName} passes a check and implementors
- * should never drop requests unless {@link onCancel} is called or the service dies.
- *
- * <p> Requests should not be queued and additional calls while expecting a result for
- * {@code packageName} should have no effect.
- */
- public abstract void onRequestHealthCheck(@NonNull String packageName);
-
- /**
- * Called when the system cancels the explicit health check request for {@code packageName}.
- * Should do nothing if there are is no active request for {@code packageName}.
- */
- public abstract void onCancelHealthCheck(@NonNull String packageName);
-
- /**
- * Called when the system requests for all the packages supporting explicit health checks. The
- * system may request an explicit health check for any of these packages with
- * {@link #onRequestHealthCheck}.
- *
- * @return all packages supporting explicit health checks
- */
- @NonNull public abstract List<PackageConfig> onGetSupportedPackages();
-
- /**
- * Called when the system requests for all the packages that it has currently requested
- * an explicit health check for.
- *
- * @return all packages expecting an explicit health check result
- */
- @NonNull public abstract List<String> onGetRequestedPackages();
-
- private final Handler mHandler = Handler.createAsync(Looper.getMainLooper());
- @Nullable private Consumer<Bundle> mHealthCheckResultCallback;
- @Nullable private Executor mCallbackExecutor;
-
- @Override
- @NonNull
- public final IBinder onBind(@NonNull Intent intent) {
- return mWrapper;
- }
-
- /**
- * Sets a callback to be invoked when an explicit health check passes for a package.
- * <p>
- * The callback will receive a {@link Bundle} containing the package name that passed the
- * health check, identified by the key {@link #EXTRA_HEALTH_CHECK_PASSED_PACKAGE}.
- * <p>
- * <b>Note:</b> This API is primarily intended for testing purposes. Calling this outside of a
- * test environment will override the default callback mechanism used to notify the system
- * about health check results. Use with caution in production code.
- *
- * @param executor The executor on which the callback should be invoked. If {@code null}, the
- * callback will be executed on the main thread.
- * @param callback A callback that receives a {@link Bundle} containing the package name that
- * passed the health check.
- */
- @FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
- public final void setHealthCheckPassedCallback(@CallbackExecutor @Nullable Executor executor,
- @Nullable Consumer<Bundle> callback) {
- mCallbackExecutor = executor;
- mHealthCheckResultCallback = callback;
- }
-
- private void executeCallback(@NonNull String packageName) {
- if (mHealthCheckResultCallback != null) {
- Objects.requireNonNull(packageName,
- "Package passing explicit health check must be non-null");
- Bundle bundle = new Bundle();
- bundle.putString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE, packageName);
- mHealthCheckResultCallback.accept(bundle);
- } else {
- Log.wtf(TAG, "System missed explicit health check result for " + packageName);
- }
- }
-
- /**
- * Implementors should call this to notify the system when explicit health check passes
- * for {@code packageName};
- */
- public final void notifyHealthCheckPassed(@NonNull String packageName) {
- if (mCallbackExecutor != null) {
- mCallbackExecutor.execute(() -> executeCallback(packageName));
- } else {
- mHandler.post(() -> executeCallback(packageName));
- }
- }
-
- /**
- * A PackageConfig contains a package supporting explicit health checks and the
- * timeout in {@link System#uptimeMillis} across reboots after which health
- * check requests from clients are failed.
- *
- * @hide
- */
- @SystemApi
- public static final class PackageConfig implements Parcelable {
- private static final long DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(1);
-
- private final String mPackageName;
- private final long mHealthCheckTimeoutMillis;
-
- /**
- * Creates a new instance.
- *
- * @param packageName the package name
- * @param durationMillis the duration in milliseconds, must be greater than or
- * equal to 0. If it is 0, it will use a system default value.
- */
- public PackageConfig(@NonNull String packageName, long healthCheckTimeoutMillis) {
- mPackageName = Preconditions.checkNotNull(packageName);
- if (healthCheckTimeoutMillis == 0) {
- mHealthCheckTimeoutMillis = DEFAULT_HEALTH_CHECK_TIMEOUT_MILLIS;
- } else {
- mHealthCheckTimeoutMillis = Preconditions.checkArgumentNonnegative(
- healthCheckTimeoutMillis);
- }
- }
-
- private PackageConfig(Parcel parcel) {
- mPackageName = parcel.readString();
- mHealthCheckTimeoutMillis = parcel.readLong();
- }
-
- /**
- * Gets the package name.
- *
- * @return the package name
- */
- public @NonNull String getPackageName() {
- return mPackageName;
- }
-
- /**
- * Gets the timeout in milliseconds to evaluate an explicit health check result after a
- * request.
- *
- * @return the duration in {@link System#uptimeMillis} across reboots
- */
- public long getHealthCheckTimeoutMillis() {
- return mHealthCheckTimeoutMillis;
- }
-
- @NonNull
- @Override
- public String toString() {
- return "PackageConfig{" + mPackageName + ", " + mHealthCheckTimeoutMillis + "}";
- }
-
- @Override
- public boolean equals(@Nullable Object other) {
- if (other == this) {
- return true;
- }
- if (!(other instanceof PackageConfig)) {
- return false;
- }
-
- PackageConfig otherInfo = (PackageConfig) other;
- return Objects.equals(otherInfo.getHealthCheckTimeoutMillis(),
- mHealthCheckTimeoutMillis)
- && Objects.equals(otherInfo.getPackageName(), mPackageName);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mPackageName, mHealthCheckTimeoutMillis);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@SuppressLint({"MissingNullability"}) Parcel parcel, int flags) {
- parcel.writeString(mPackageName);
- parcel.writeLong(mHealthCheckTimeoutMillis);
- }
-
- public static final @NonNull Creator<PackageConfig> CREATOR = new Creator<PackageConfig>() {
- @Override
- public PackageConfig createFromParcel(Parcel source) {
- return new PackageConfig(source);
- }
-
- @Override
- public PackageConfig[] newArray(int size) {
- return new PackageConfig[size];
- }
- };
- }
-
-
- private class ExplicitHealthCheckServiceWrapper extends IExplicitHealthCheckService.Stub {
- @Override
- public void setCallback(RemoteCallback callback) throws RemoteException {
- mHandler.post(() -> mHealthCheckResultCallback = callback::sendResult);
- }
-
- @Override
- public void request(String packageName) throws RemoteException {
- mHandler.post(() -> ExplicitHealthCheckService.this.onRequestHealthCheck(packageName));
- }
-
- @Override
- public void cancel(String packageName) throws RemoteException {
- mHandler.post(() -> ExplicitHealthCheckService.this.onCancelHealthCheck(packageName));
- }
-
- @Override
- public void getSupportedPackages(RemoteCallback callback) throws RemoteException {
- mHandler.post(() -> {
- List<PackageConfig> packages =
- ExplicitHealthCheckService.this.onGetSupportedPackages();
- Objects.requireNonNull(packages, "Supported package list must be non-null");
- Bundle bundle = new Bundle();
- bundle.putParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, new ArrayList<>(packages));
- callback.sendResult(bundle);
- });
- }
-
- @Override
- public void getRequestedPackages(RemoteCallback callback) throws RemoteException {
- mHandler.post(() -> {
- List<String> packages =
- ExplicitHealthCheckService.this.onGetRequestedPackages();
- Objects.requireNonNull(packages, "Requested package list must be non-null");
- Bundle bundle = new Bundle();
- bundle.putStringArrayList(EXTRA_REQUESTED_PACKAGES, new ArrayList<>(packages));
- callback.sendResult(bundle);
- });
- }
- }
-}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS b/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
deleted file mode 100644
index 1c045e10c0ec..000000000000
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-narayan@google.com
-nandana@google.com
-olilan@google.com
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java b/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
deleted file mode 100644
index da9a13961f79..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/ExplicitHealthCheckController.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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;
-
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_HEALTH_CHECK_PASSED_PACKAGE;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_REQUESTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.EXTRA_SUPPORTED_PACKAGES;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-
-import android.Manifest;
-import android.annotation.MainThread;
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.service.watchdog.ExplicitHealthCheckService;
-import android.service.watchdog.IExplicitHealthCheckService;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Consumer;
-
-// TODO(b/120598832): Add tests
-/**
- * Controls the connections with {@link ExplicitHealthCheckService}.
- */
-class ExplicitHealthCheckController {
- private static final String TAG = "ExplicitHealthCheckController";
- private final Object mLock = new Object();
- private final Context mContext;
-
- // Called everytime a package passes the health check, so the watchdog is notified of the
- // passing check. In practice, should never be null after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
- @GuardedBy("mLock") @Nullable private Consumer<String> mPassedConsumer;
- // Called everytime after a successful #syncRequest call, so the watchdog can receive packages
- // supporting health checks and update its internal state. In practice, should never be null
- // after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this consumer.
- @GuardedBy("mLock") @Nullable private Consumer<List<PackageConfig>> mSupportedConsumer;
- // Called everytime we need to notify the watchdog to sync requests between itself and the
- // health check service. In practice, should never be null after it has been #setEnabled.
- // To prevent deadlocks between the controller and watchdog threads, we have
- // a lock invariant to ALWAYS acquire the PackageWatchdog#mLock before #mLock in this class.
- // It's easier to just NOT hold #mLock when calling into watchdog code on this runnable.
- @GuardedBy("mLock") @Nullable private Runnable mNotifySyncRunnable;
- // Actual binder object to the explicit health check service.
- @GuardedBy("mLock") @Nullable private IExplicitHealthCheckService mRemoteService;
- // Connection to the explicit health check service, necessary to unbind.
- // We should only try to bind if mConnection is null, non-null indicates we
- // are connected or at least connecting.
- @GuardedBy("mLock") @Nullable private ServiceConnection mConnection;
- // Bind state of the explicit health check service.
- @GuardedBy("mLock") private boolean mEnabled;
-
- ExplicitHealthCheckController(Context context) {
- mContext = context;
- }
-
- /** Enables or disables explicit health checks. */
- public void setEnabled(boolean enabled) {
- synchronized (mLock) {
- Slog.i(TAG, "Explicit health checks " + (enabled ? "enabled." : "disabled."));
- mEnabled = enabled;
- }
- }
-
- /**
- * Sets callbacks to listen to important events from the controller.
- *
- * <p> Should be called once at initialization before any other calls to the controller to
- * ensure a happens-before relationship of the set parameters and visibility on other threads.
- */
- public void setCallbacks(Consumer<String> passedConsumer,
- Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
- synchronized (mLock) {
- if (mPassedConsumer != null || mSupportedConsumer != null
- || mNotifySyncRunnable != null) {
- Slog.wtf(TAG, "Resetting health check controller callbacks");
- }
-
- mPassedConsumer = Objects.requireNonNull(passedConsumer);
- mSupportedConsumer = Objects.requireNonNull(supportedConsumer);
- mNotifySyncRunnable = Objects.requireNonNull(notifySyncRunnable);
- }
- }
-
- /**
- * Calls the health check service to request or cancel packages based on
- * {@code newRequestedPackages}.
- *
- * <p> Supported packages in {@code newRequestedPackages} that have not been previously
- * requested will be requested while supported packages not in {@code newRequestedPackages}
- * but were previously requested will be cancelled.
- *
- * <p> This handles binding and unbinding to the health check service as required.
- *
- * <p> Note, calling this may modify {@code newRequestedPackages}.
- *
- * <p> Note, this method is not thread safe, all calls should be serialized.
- */
- public void syncRequests(Set<String> newRequestedPackages) {
- boolean enabled;
- synchronized (mLock) {
- enabled = mEnabled;
- }
-
- if (!enabled) {
- Slog.i(TAG, "Health checks disabled, no supported packages");
- // Call outside lock
- mSupportedConsumer.accept(Collections.emptyList());
- return;
- }
-
- getSupportedPackages(supportedPackageConfigs -> {
- // Notify the watchdog without lock held
- mSupportedConsumer.accept(supportedPackageConfigs);
- getRequestedPackages(previousRequestedPackages -> {
- synchronized (mLock) {
- // Hold lock so requests and cancellations are sent atomically.
- // It is important we don't mix requests from multiple threads.
-
- Set<String> supportedPackages = new ArraySet<>();
- for (PackageConfig config : supportedPackageConfigs) {
- supportedPackages.add(config.getPackageName());
- }
- // Note, this may modify newRequestedPackages
- newRequestedPackages.retainAll(supportedPackages);
-
- // Cancel packages no longer requested
- actOnDifference(previousRequestedPackages,
- newRequestedPackages, p -> cancel(p));
- // Request packages not yet requested
- actOnDifference(newRequestedPackages,
- previousRequestedPackages, p -> request(p));
-
- if (newRequestedPackages.isEmpty()) {
- Slog.i(TAG, "No more health check requests, unbinding...");
- unbindService();
- return;
- }
- }
- });
- });
- }
-
- private void actOnDifference(Collection<String> collection1, Collection<String> collection2,
- Consumer<String> action) {
- Iterator<String> iterator = collection1.iterator();
- while (iterator.hasNext()) {
- String packageName = iterator.next();
- if (!collection2.contains(packageName)) {
- action.accept(packageName);
- }
- }
- }
-
- /**
- * Requests an explicit health check for {@code packageName}.
- * After this request, the callback registered on {@link #setCallbacks} can receive explicit
- * health check passed results.
- */
- private void request(String packageName) {
- synchronized (mLock) {
- if (!prepareServiceLocked("request health check for " + packageName)) {
- return;
- }
-
- Slog.i(TAG, "Requesting health check for package " + packageName);
- try {
- mRemoteService.request(packageName);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to request health check for package " + packageName, e);
- }
- }
- }
-
- /**
- * Cancels all explicit health checks for {@code packageName}.
- * After this request, the callback registered on {@link #setCallbacks} can no longer receive
- * explicit health check passed results.
- */
- private void cancel(String packageName) {
- synchronized (mLock) {
- if (!prepareServiceLocked("cancel health check for " + packageName)) {
- return;
- }
-
- Slog.i(TAG, "Cancelling health check for package " + packageName);
- try {
- mRemoteService.cancel(packageName);
- } catch (RemoteException e) {
- // Do nothing, if the service is down, when it comes up, we will sync requests,
- // if there's some other error, retrying wouldn't fix anyways.
- Slog.w(TAG, "Failed to cancel health check for package " + packageName, e);
- }
- }
- }
-
- /**
- * Returns the packages that we can request explicit health checks for.
- * The packages will be returned to the {@code consumer}.
- */
- private void getSupportedPackages(Consumer<List<PackageConfig>> consumer) {
- synchronized (mLock) {
- if (!prepareServiceLocked("get health check supported packages")) {
- return;
- }
-
- Slog.d(TAG, "Getting health check supported packages");
- try {
- mRemoteService.getSupportedPackages(new RemoteCallback(result -> {
- List<PackageConfig> packages =
- result.getParcelableArrayList(EXTRA_SUPPORTED_PACKAGES, android.service.watchdog.ExplicitHealthCheckService.PackageConfig.class);
- Slog.i(TAG, "Explicit health check supported packages " + packages);
- consumer.accept(packages);
- }));
- } catch (RemoteException e) {
- // Request failed, treat as if all observed packages are supported, if any packages
- // expire during this period, we may incorrectly treat it as failing health checks
- // even if we don't support health checks for the package.
- Slog.w(TAG, "Failed to get health check supported packages", e);
- }
- }
- }
-
- /**
- * Returns the packages for which health checks are currently in progress.
- * The packages will be returned to the {@code consumer}.
- */
- private void getRequestedPackages(Consumer<List<String>> consumer) {
- synchronized (mLock) {
- if (!prepareServiceLocked("get health check requested packages")) {
- return;
- }
-
- Slog.d(TAG, "Getting health check requested packages");
- try {
- mRemoteService.getRequestedPackages(new RemoteCallback(result -> {
- List<String> packages = result.getStringArrayList(EXTRA_REQUESTED_PACKAGES);
- Slog.i(TAG, "Explicit health check requested packages " + packages);
- consumer.accept(packages);
- }));
- } catch (RemoteException e) {
- // Request failed, treat as if we haven't requested any packages, if any packages
- // were actually requested, they will not be cancelled now. May be cancelled later
- Slog.w(TAG, "Failed to get health check requested packages", e);
- }
- }
- }
-
- /**
- * Binds to the explicit health check service if the controller is enabled and
- * not already bound.
- */
- private void bindService() {
- synchronized (mLock) {
- if (!mEnabled || mConnection != null || mRemoteService != null) {
- if (!mEnabled) {
- Slog.i(TAG, "Not binding to service, service disabled");
- } else if (mRemoteService != null) {
- Slog.i(TAG, "Not binding to service, service already connected");
- } else {
- Slog.i(TAG, "Not binding to service, service already connecting");
- }
- return;
- }
- ComponentName component = getServiceComponentNameLocked();
- if (component == null) {
- Slog.wtf(TAG, "Explicit health check service not found");
- return;
- }
-
- Intent intent = new Intent();
- intent.setComponent(component);
- mConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- Slog.i(TAG, "Explicit health check service is connected " + name);
- initState(service);
- }
-
- @Override
- @MainThread
- public void onServiceDisconnected(ComponentName name) {
- // Service crashed or process was killed, #onServiceConnected will be called.
- // Don't need to re-bind.
- Slog.i(TAG, "Explicit health check service is disconnected " + name);
- synchronized (mLock) {
- mRemoteService = null;
- }
- }
-
- @Override
- public void onBindingDied(ComponentName name) {
- // Application hosting service probably got updated
- // Need to re-bind.
- Slog.i(TAG, "Explicit health check service binding is dead. Rebind: " + name);
- unbindService();
- bindService();
- }
-
- @Override
- public void onNullBinding(ComponentName name) {
- // Should never happen. Service returned null from #onBind.
- Slog.wtf(TAG, "Explicit health check service binding is null?? " + name);
- }
- };
-
- mContext.bindServiceAsUser(intent, mConnection,
- Context.BIND_AUTO_CREATE, UserHandle.SYSTEM);
- Slog.i(TAG, "Explicit health check service is bound");
- }
- }
-
- /** Unbinds the explicit health check service. */
- private void unbindService() {
- synchronized (mLock) {
- if (mRemoteService != null) {
- mContext.unbindService(mConnection);
- mRemoteService = null;
- mConnection = null;
- }
- Slog.i(TAG, "Explicit health check service is unbound");
- }
- }
-
- @GuardedBy("mLock")
- @Nullable
- private ServiceInfo getServiceInfoLocked() {
- final Intent intent = new Intent(ExplicitHealthCheckService.SERVICE_INTERFACE);
- final ResolveInfo resolveInfo = mContext.getPackageManager().resolveService(intent,
- PackageManager.GET_SERVICES | PackageManager.GET_META_DATA
- | PackageManager.MATCH_SYSTEM_ONLY);
- if (resolveInfo == null || resolveInfo.serviceInfo == null) {
- Slog.w(TAG, "No valid components found.");
- return null;
- }
- return resolveInfo.serviceInfo;
- }
-
- @GuardedBy("mLock")
- @Nullable
- private ComponentName getServiceComponentNameLocked() {
- final ServiceInfo serviceInfo = getServiceInfoLocked();
- if (serviceInfo == null) {
- return null;
- }
-
- final ComponentName name = new ComponentName(serviceInfo.packageName, serviceInfo.name);
- if (!Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE
- .equals(serviceInfo.permission)) {
- Slog.w(TAG, name.flattenToShortString() + " does not require permission "
- + Manifest.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE);
- return null;
- }
- return name;
- }
-
- private void initState(IBinder service) {
- synchronized (mLock) {
- if (!mEnabled) {
- Slog.w(TAG, "Attempting to connect disabled service?? Unbinding...");
- // Very unlikely, but we disabled the service after binding but before we connected
- unbindService();
- return;
- }
- mRemoteService = IExplicitHealthCheckService.Stub.asInterface(service);
- try {
- mRemoteService.setCallback(new RemoteCallback(result -> {
- String packageName = result.getString(EXTRA_HEALTH_CHECK_PASSED_PACKAGE);
- if (!TextUtils.isEmpty(packageName)) {
- if (mPassedConsumer == null) {
- Slog.wtf(TAG, "Health check passed for package " + packageName
- + "but no consumer registered.");
- } else {
- // Call without lock held
- mPassedConsumer.accept(packageName);
- }
- } else {
- Slog.wtf(TAG, "Empty package passed explicit health check?");
- }
- }));
- Slog.i(TAG, "Service initialized, syncing requests");
- } catch (RemoteException e) {
- Slog.wtf(TAG, "Could not setCallback on explicit health check service");
- }
- }
- // Calling outside lock
- mNotifySyncRunnable.run();
- }
-
- /**
- * Prepares the health check service to receive requests.
- *
- * @return {@code true} if it is ready and we can proceed with a request,
- * {@code false} otherwise. If it is not ready, and the service is enabled,
- * we will bind and the request should be automatically attempted later.
- */
- @GuardedBy("mLock")
- private boolean prepareServiceLocked(String action) {
- if (mRemoteService != null && mEnabled) {
- return true;
- }
- Slog.i(TAG, "Service not ready to " + action
- + (mEnabled ? ". Binding..." : ". Disabled"));
- if (mEnabled) {
- bindService();
- }
- return false;
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java b/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
deleted file mode 100644
index e4f07f9fc213..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/PackageWatchdog.java
+++ /dev/null
@@ -1,2253 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static android.content.Intent.ACTION_REBOOT;
-import static android.content.Intent.ACTION_SHUTDOWN;
-import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
-import static android.util.Xml.Encoding.UTF_8;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.dumpCrashRecoveryEvents;
-
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.FlaggedApi;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Process;
-import android.os.SystemProperties;
-import android.provider.DeviceConfig;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.EventLog;
-import android.util.IndentingPrintWriter;
-import android.util.LongArrayQueue;
-import android.util.Slog;
-import android.util.Xml;
-import android.util.XmlUtils;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
-import com.android.modules.utils.BackgroundThread;
-
-import libcore.io.IoUtils;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Monitors the health of packages on the system and notifies interested observers when packages
- * fail. On failure, the registered observer with the least user impacting mitigation will
- * be notified.
- * @hide
- */
-@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public class PackageWatchdog {
- private static final String TAG = "PackageWatchdog";
-
- static final String PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS =
- "watchdog_trigger_failure_duration_millis";
- static final String PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT =
- "watchdog_trigger_failure_count";
- static final String PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED =
- "watchdog_explicit_health_check_enabled";
-
- // TODO: make the following values configurable via DeviceConfig
- private static final long NATIVE_CRASH_POLLING_INTERVAL_MILLIS =
- TimeUnit.SECONDS.toMillis(30);
- private static final long NUMBER_OF_NATIVE_CRASH_POLLS = 10;
-
-
- /** Reason for package failure could not be determined. */
- public static final int FAILURE_REASON_UNKNOWN = 0;
-
- /** The package had a native crash. */
- public static final int FAILURE_REASON_NATIVE_CRASH = 1;
-
- /** The package failed an explicit health check. */
- public static final int FAILURE_REASON_EXPLICIT_HEALTH_CHECK = 2;
-
- /** The app crashed. */
- public static final int FAILURE_REASON_APP_CRASH = 3;
-
- /** The app was not responding. */
- public static final int FAILURE_REASON_APP_NOT_RESPONDING = 4;
-
- /** The device was boot looping. */
- public static final int FAILURE_REASON_BOOT_LOOP = 5;
-
- /** @hide */
- @IntDef(prefix = { "FAILURE_REASON_" }, value = {
- FAILURE_REASON_UNKNOWN,
- FAILURE_REASON_NATIVE_CRASH,
- FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
- FAILURE_REASON_APP_CRASH,
- FAILURE_REASON_APP_NOT_RESPONDING,
- FAILURE_REASON_BOOT_LOOP
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface FailureReasons {}
-
- // Duration to count package failures before it resets to 0
- @VisibleForTesting
- static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
- (int) TimeUnit.MINUTES.toMillis(1);
- // Number of package failures within the duration above before we notify observers
- @VisibleForTesting
- static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;
- @VisibleForTesting
- static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
- // Sliding window for tracking how many mitigation calls were made for a package.
- @VisibleForTesting
- static final long DEFAULT_DEESCALATION_WINDOW_MS = TimeUnit.HOURS.toMillis(1);
- // Whether explicit health checks are enabled or not
- private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
-
- @VisibleForTesting
- static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
-
- static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
-
- // Time needed to apply mitigation
- private static final String MITIGATION_WINDOW_MS =
- "persist.device_config.configuration.mitigation_window_ms";
- @VisibleForTesting
- static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
-
- // Threshold level at which or above user might experience significant disruption.
- private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
- "persist.device_config.configuration.major_user_impact_level_threshold";
- private static final int DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-
- // Comma separated list of all packages exempt from user impact level threshold. If a package
- // in the list is crash looping, all the mitigations including factory reset will be performed.
- private static final String PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
- "persist.device_config.configuration.packages_exempt_from_impact_level_threshold";
-
- // Comma separated list of default packages exempt from user impact level threshold.
- private static final String DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD =
- "com.android.systemui";
-
- private long mNumberOfNativeCrashPollsRemaining;
-
- private static final int DB_VERSION = 1;
- private static final String TAG_PACKAGE_WATCHDOG = "package-watchdog";
- private static final String TAG_PACKAGE = "package";
- private static final String TAG_OBSERVER = "observer";
- private static final String ATTR_VERSION = "version";
- private static final String ATTR_NAME = "name";
- private static final String ATTR_DURATION = "duration";
- private static final String ATTR_EXPLICIT_HEALTH_CHECK_DURATION = "health-check-duration";
- private static final String ATTR_PASSED_HEALTH_CHECK = "passed-health-check";
- private static final String ATTR_MITIGATION_CALLS = "mitigation-calls";
- private static final String ATTR_MITIGATION_COUNT = "mitigation-count";
-
- // A file containing information about the current mitigation count in the case of a boot loop.
- // This allows boot loop information to persist in the case of an fs-checkpoint being
- // aborted.
- private static final String METADATA_FILE = "/metadata/watchdog/mitigation_count.txt";
-
- /**
- * EventLog tags used when logging into the event log. Note the values must be sync with
- * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
- * name translation.
- */
- private static final int LOG_TAG_RESCUE_NOTE = 2900;
-
- private static final Object sPackageWatchdogLock = new Object();
- @GuardedBy("sPackageWatchdogLock")
- private static PackageWatchdog sPackageWatchdog;
-
- private static final Object sLock = new Object();
- // System server context
- private final Context mContext;
- // Handler to run short running tasks
- private final Handler mShortTaskHandler;
- // Handler for processing IO and long running tasks
- private final Handler mLongTaskHandler;
- // Contains (observer-name -> observer-handle) that have ever been registered from
- // previous boots. Observers with all packages expired are periodically pruned.
- // It is saved to disk on system shutdown and repouplated on startup so it survives reboots.
- @GuardedBy("sLock")
- private final ArrayMap<String, ObserverInternal> mAllObservers = new ArrayMap<>();
- // File containing the XML data of monitored packages /data/system/package-watchdog.xml
- private final AtomicFile mPolicyFile;
- private final ExplicitHealthCheckController mHealthCheckController;
- private final Runnable mSyncRequests = this::syncRequests;
- private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
- private final Runnable mSaveToFile = this::saveToFile;
- private final SystemClock mSystemClock;
- private final BootThreshold mBootThreshold;
- private final DeviceConfig.OnPropertiesChangedListener
- mOnPropertyChangedListener = this::onPropertyChanged;
-
- private final Set<String> mPackagesExemptFromImpactLevelThreshold = new ArraySet<>();
-
- // The set of packages that have been synced with the ExplicitHealthCheckController
- @GuardedBy("sLock")
- private Set<String> mRequestedHealthCheckPackages = new ArraySet<>();
- @GuardedBy("sLock")
- private boolean mIsPackagesReady;
- // Flag to control whether explicit health checks are supported or not
- @GuardedBy("sLock")
- private boolean mIsHealthCheckEnabled = DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED;
- @GuardedBy("sLock")
- private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
- @GuardedBy("sLock")
- private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
- // SystemClock#uptimeMillis when we last executed #syncState
- // 0 if no prune is scheduled.
- @GuardedBy("sLock")
- private long mUptimeAtLastStateSync;
- // If true, sync explicit health check packages with the ExplicitHealthCheckController.
- @GuardedBy("sLock")
- private boolean mSyncRequired = false;
-
- @GuardedBy("sLock")
- private long mLastMitigation = -1000000;
-
- @FunctionalInterface
- @VisibleForTesting
- interface SystemClock {
- long uptimeMillis();
- }
-
- private PackageWatchdog(Context context) {
- // Needs to be constructed inline
- this(context, new AtomicFile(
- new File(new File(Environment.getDataDirectory(), "system"),
- "package-watchdog.xml")),
- new Handler(Looper.myLooper()), BackgroundThread.getHandler(),
- new ExplicitHealthCheckController(context),
- android.os.SystemClock::uptimeMillis);
- }
-
- /**
- * Creates a PackageWatchdog that allows injecting dependencies.
- */
- @VisibleForTesting
- PackageWatchdog(Context context, AtomicFile policyFile, Handler shortTaskHandler,
- Handler longTaskHandler, ExplicitHealthCheckController controller,
- SystemClock clock) {
- mContext = context;
- mPolicyFile = policyFile;
- mShortTaskHandler = shortTaskHandler;
- mLongTaskHandler = longTaskHandler;
- mHealthCheckController = controller;
- mSystemClock = clock;
- mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
- mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
- DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
-
- loadFromFile();
- sPackageWatchdog = this;
- }
-
- /**
- * Creates or gets singleton instance of PackageWatchdog.
- *
- * @param context The system server context.
- */
- public static @NonNull PackageWatchdog getInstance(@NonNull Context context) {
- synchronized (sPackageWatchdogLock) {
- if (sPackageWatchdog == null) {
- new PackageWatchdog(context);
- }
- return sPackageWatchdog;
- }
- }
-
- /**
- * Called during boot to notify when packages are ready on the device so we can start
- * binding.
- * @hide
- */
- public void onPackagesReady() {
- synchronized (sLock) {
- mIsPackagesReady = true;
- mHealthCheckController.setCallbacks(packageName -> onHealthCheckPassed(packageName),
- packages -> onSupportedPackages(packages),
- this::onSyncRequestNotified);
- setPropertyChangedListenerLocked();
- updateConfigs();
- }
- }
-
- /**
- * Registers {@code observer} to listen for package failures. Add a new ObserverInternal for
- * this observer if it does not already exist.
- * For executing mitigations observers will receive callback on the given executor.
- *
- * <p>Observers are expected to call this on boot. It does not specify any packages but
- * it will resume observing any packages requested from a previous boot.
- *
- * @param observer instance of {@link PackageHealthObserver} for observing package failures
- * and boot loops.
- * @param executor Executor for the thread on which observers would receive callbacks
- */
- public void registerHealthObserver(@NonNull @CallbackExecutor Executor executor,
- @NonNull PackageHealthObserver observer) {
- synchronized (sLock) {
- ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
- if (internalObserver != null) {
- internalObserver.registeredObserver = observer;
- internalObserver.observerExecutor = executor;
- } else {
- internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
- new ArrayList<>());
- internalObserver.registeredObserver = observer;
- internalObserver.observerExecutor = executor;
- mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
- syncState("added new observer");
- }
- }
- }
-
- /**
- * Starts observing the health of the {@code packages} for {@code observer}.
- * Note: Observer needs to be registered with {@link #registerHealthObserver} before calling
- * this API.
- *
- * <p>If monitoring a package supporting explicit health check, at the end of the monitoring
- * duration if {@link #onHealthCheckPassed} was never called,
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} will be called as if the
- * package failed.
- *
- * <p>If {@code observer} is already monitoring a package in {@code packageNames},
- * the monitoring window of that package will be reset to {@code durationMs} and the health
- * check state will be reset to a default.
- *
- * <p>The {@code observer} must be registered with {@link #registerHealthObserver} before
- * calling this method.
- *
- * @param packageNames The list of packages to check. If this is empty, the call will be a
- * no-op.
- *
- * @param timeoutMs The timeout after which Explicit Health Checks would not run. If this is
- * less than 1, a default monitoring duration 2 days will be used.
- *
- * @throws IllegalStateException if the observer was not previously registered
- */
- public void startExplicitHealthCheck(@NonNull List<String> packageNames, long timeoutMs,
- @NonNull PackageHealthObserver observer) {
- synchronized (sLock) {
- if (!mAllObservers.containsKey(observer.getUniqueIdentifier())) {
- Slog.wtf(TAG, "No observer found, need to register the observer: "
- + observer.getUniqueIdentifier());
- throw new IllegalStateException("Observer not registered");
- }
- }
- if (packageNames.isEmpty()) {
- Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
- return;
- }
- if (timeoutMs < 1) {
- Slog.wtf(TAG, "Invalid duration " + timeoutMs + "ms for observer "
- + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
- timeoutMs = DEFAULT_OBSERVING_DURATION_MS;
- }
-
- List<MonitoredPackage> packages = new ArrayList<>();
- for (int i = 0; i < packageNames.size(); i++) {
- // Health checks not available yet so health check state will start INACTIVE
- MonitoredPackage pkg = newMonitoredPackage(packageNames.get(i), timeoutMs, false);
- if (pkg != null) {
- packages.add(pkg);
- } else {
- Slog.w(TAG, "Failed to create MonitoredPackage for pkg=" + packageNames.get(i));
- }
- }
-
- if (packages.isEmpty()) {
- return;
- }
-
- // Sync before we add the new packages to the observers. This will #pruneObservers,
- // causing any elapsed time to be deducted from all existing packages before we add new
- // packages. This maintains the invariant that the elapsed time for ALL (new and existing)
- // packages is the same.
- mLongTaskHandler.post(() -> {
- syncState("observing new packages");
-
- synchronized (sLock) {
- ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
- if (oldObserver == null) {
- Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
- + "of packages " + packageNames);
- mAllObservers.put(observer.getUniqueIdentifier(),
- new ObserverInternal(observer.getUniqueIdentifier(), packages));
- } else {
- Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
- + "packages to monitor " + packageNames);
- oldObserver.updatePackagesLocked(packages);
- }
- }
-
- // Sync after we add the new packages to the observers. We may have received packges
- // requiring an earlier schedule than we are currently scheduled for.
- syncState("updated observers");
- });
-
- }
-
- /**
- * Unregisters {@code observer} from listening to package failure.
- * Additionally, this stops observing any packages that may have previously been observed
- * even from a previous boot.
- */
- public void unregisterHealthObserver(@NonNull PackageHealthObserver observer) {
- mLongTaskHandler.post(() -> {
- synchronized (sLock) {
- mAllObservers.remove(observer.getUniqueIdentifier());
- }
- syncState("unregistering observer: " + observer.getUniqueIdentifier());
- });
- }
-
- /**
- * Called when a process fails due to a crash, ANR or explicit health check.
- *
- * <p>For each package contained in the process, one registered observer with the least user
- * impact will be notified for mitigation.
- *
- * <p>This method could be called frequently if there is a severe problem on the device.
- */
- public void notifyPackageFailure(@NonNull List<VersionedPackage> packages,
- @FailureReasons int failureReason) {
- if (packages == null) {
- Slog.w(TAG, "Could not resolve a list of failing packages");
- return;
- }
- synchronized (sLock) {
- final long now = mSystemClock.uptimeMillis();
- if (Flags.recoverabilityDetection()) {
- if (now >= mLastMitigation
- && (now - mLastMitigation) < getMitigationWindowMs()) {
- Slog.i(TAG, "Skipping notifyPackageFailure mitigation");
- return;
- }
- }
- }
- mLongTaskHandler.post(() -> {
- synchronized (sLock) {
- if (mAllObservers.isEmpty()) {
- return;
- }
- boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
- || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- if (requiresImmediateAction) {
- handleFailureImmediately(packages, failureReason);
- } else {
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- VersionedPackage versionedPackage = packages.get(pIndex);
- // Observer that will receive failure for versionedPackage
- ObserverInternal currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- MonitoredPackage currentMonitoredPackage = null;
-
- // Find observer with least user impact
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- ObserverInternal observer = mAllObservers.valueAt(oIndex);
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null
- && observer.notifyPackageFailureLocked(
- versionedPackage.getPackageName())) {
- MonitoredPackage p = observer.getMonitoredPackage(
- versionedPackage.getPackageName());
- int mitigationCount = 1;
- if (p != null) {
- mitigationCount = p.getMitigationCountLocked() + 1;
- }
- int impact = registeredObserver.onHealthCheckFailed(
- versionedPackage, failureReason, mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = observer;
- currentObserverImpact = impact;
- currentMonitoredPackage = p;
- }
- }
- }
-
- // Execute action with least user impact
- if (currentObserverToNotify != null) {
- int mitigationCount;
- if (currentMonitoredPackage != null) {
- currentMonitoredPackage.noteMitigationCallLocked();
- mitigationCount =
- currentMonitoredPackage.getMitigationCountLocked();
- } else {
- mitigationCount = 1;
- }
- if (Flags.recoverabilityDetection()) {
- maybeExecute(currentObserverToNotify, versionedPackage,
- failureReason, currentObserverImpact, mitigationCount);
- } else {
- PackageHealthObserver registeredObserver =
- currentObserverToNotify.registeredObserver;
- currentObserverToNotify.observerExecutor.execute(() ->
- registeredObserver.onExecuteHealthCheckMitigation(
- versionedPackage, failureReason, mitigationCount));
- }
- }
- }
- }
- }
- });
- }
-
- /**
- * For native crashes or explicit health check failures, call directly into each observer to
- * mitigate the error without going through failure threshold logic.
- */
- @GuardedBy("sLock")
- private void handleFailureImmediately(List<VersionedPackage> packages,
- @FailureReasons int failureReason) {
- VersionedPackage failingPackage = packages.size() > 0 ? packages.get(0) : null;
- ObserverInternal currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- for (ObserverInternal observer: mAllObservers.values()) {
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- int impact = registeredObserver.onHealthCheckFailed(
- failingPackage, failureReason, 1);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = observer;
- currentObserverImpact = impact;
- }
- }
- }
- if (currentObserverToNotify != null) {
- if (Flags.recoverabilityDetection()) {
- maybeExecute(currentObserverToNotify, failingPackage, failureReason,
- currentObserverImpact, /*mitigationCount=*/ 1);
- } else {
- PackageHealthObserver registeredObserver =
- currentObserverToNotify.registeredObserver;
- currentObserverToNotify.observerExecutor.execute(() ->
- registeredObserver.onExecuteHealthCheckMitigation(failingPackage,
- failureReason, 1));
-
- }
- }
- }
-
- private void maybeExecute(ObserverInternal currentObserverToNotify,
- VersionedPackage versionedPackage,
- @FailureReasons int failureReason,
- int currentObserverImpact,
- int mitigationCount) {
- if (allowMitigations(currentObserverImpact, versionedPackage)) {
- PackageHealthObserver registeredObserver;
- synchronized (sLock) {
- mLastMitigation = mSystemClock.uptimeMillis();
- registeredObserver = currentObserverToNotify.registeredObserver;
- }
- currentObserverToNotify.observerExecutor.execute(() ->
- registeredObserver.onExecuteHealthCheckMitigation(versionedPackage,
- failureReason, mitigationCount));
- }
- }
-
- private boolean allowMitigations(int currentObserverImpact,
- VersionedPackage versionedPackage) {
- return currentObserverImpact < getUserImpactLevelLimit()
- || getPackagesExemptFromImpactLevelThreshold().contains(
- versionedPackage.getPackageName());
- }
-
- private long getMitigationWindowMs() {
- return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
- }
-
-
- /**
- * Called when the system server boots. If the system server is detected to be in a boot loop,
- * query each observer and perform the mitigation action with the lowest user impact.
- *
- * Note: PackageWatchdog considers system_server restart loop as bootloop. Full reboots
- * are not counted in bootloop.
- * @hide
- */
- @SuppressWarnings("GuardedBy")
- public void noteBoot() {
- synchronized (sLock) {
- // if boot count has reached threshold, start mitigation.
- // We wait until threshold number of restarts only for the first time. Perform
- // mitigations for every restart after that.
- boolean mitigate = mBootThreshold.incrementAndTest();
- if (mitigate) {
- if (!Flags.recoverabilityDetection()) {
- mBootThreshold.reset();
- }
- int mitigationCount = mBootThreshold.getMitigationCount() + 1;
- ObserverInternal currentObserverToNotify = null;
- int currentObserverImpact = Integer.MAX_VALUE;
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- int impact = Flags.recoverabilityDetection()
- ? registeredObserver.onBootLoop(
- observer.getBootMitigationCount() + 1)
- : registeredObserver.onBootLoop(mitigationCount);
- if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0
- && impact < currentObserverImpact) {
- currentObserverToNotify = observer;
- currentObserverImpact = impact;
- }
- }
- }
-
- if (currentObserverToNotify != null) {
- PackageHealthObserver registeredObserver =
- currentObserverToNotify.registeredObserver;
- if (Flags.recoverabilityDetection()) {
- int currentObserverMitigationCount =
- currentObserverToNotify.getBootMitigationCount() + 1;
- currentObserverToNotify.setBootMitigationCount(
- currentObserverMitigationCount);
- saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- currentObserverToNotify.observerExecutor
- .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
- currentObserverMitigationCount));
- } else {
- mBootThreshold.setMitigationCount(mitigationCount);
- mBootThreshold.saveMitigationCountToMetadata();
- currentObserverToNotify.observerExecutor
- .execute(() -> registeredObserver.onExecuteBootLoopMitigation(
- mitigationCount));
-
- }
- }
- }
- }
- }
-
- // TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
- // avoid holding lock?
- // This currently adds about 7ms extra to shutdown thread
- /** @hide Writes the package information to file during shutdown. */
- public void writeNow() {
- synchronized (sLock) {
- // Must only run synchronous tasks as this runs on the ShutdownThread and no other
- // thread is guaranteed to run during shutdown.
- if (!mAllObservers.isEmpty()) {
- mLongTaskHandler.removeCallbacks(mSaveToFile);
- pruneObserversLocked();
- saveToFile();
- Slog.i(TAG, "Last write to update package durations");
- }
- }
- }
-
- /**
- * Enables or disables explicit health checks.
- * <p> If explicit health checks are enabled, the health check service is started.
- * <p> If explicit health checks are disabled, pending explicit health check requests are
- * passed and the health check service is stopped.
- */
- private void setExplicitHealthCheckEnabled(boolean enabled) {
- synchronized (sLock) {
- mIsHealthCheckEnabled = enabled;
- mHealthCheckController.setEnabled(enabled);
- mSyncRequired = true;
- // Prune to update internal state whenever health check is enabled/disabled
- syncState("health check state " + (enabled ? "enabled" : "disabled"));
- }
- }
-
- /**
- * This method should be only called on mShortTaskHandler, since it modifies
- * {@link #mNumberOfNativeCrashPollsRemaining}.
- */
- private void checkAndMitigateNativeCrashes() {
- mNumberOfNativeCrashPollsRemaining--;
- // Check if native watchdog reported a crash
- if ("1".equals(SystemProperties.get("sys.init.updatable_crashing"))) {
- // We rollback all available low impact rollbacks when crash is unattributable
- notifyPackageFailure(Collections.EMPTY_LIST, FAILURE_REASON_NATIVE_CRASH);
- // we stop polling after an attempt to execute rollback, regardless of whether the
- // attempt succeeds or not
- } else {
- if (mNumberOfNativeCrashPollsRemaining > 0) {
- mShortTaskHandler.postDelayed(() -> checkAndMitigateNativeCrashes(),
- NATIVE_CRASH_POLLING_INTERVAL_MILLIS);
- }
- }
- }
-
- /**
- * Since this method can eventually trigger a rollback, it should be called
- * only once boot has completed {@code onBootCompleted} and not earlier, because the install
- * session must be entirely completed before we try to rollback.
- * @hide
- */
- public void scheduleCheckAndMitigateNativeCrashes() {
- Slog.i(TAG, "Scheduling " + mNumberOfNativeCrashPollsRemaining + " polls to check "
- + "and mitigate native crashes");
- mShortTaskHandler.post(()->checkAndMitigateNativeCrashes());
- }
-
- private int getUserImpactLevelLimit() {
- return SystemProperties.getInt(MAJOR_USER_IMPACT_LEVEL_THRESHOLD,
- DEFAULT_MAJOR_USER_IMPACT_LEVEL_THRESHOLD);
- }
-
- private Set<String> getPackagesExemptFromImpactLevelThreshold() {
- if (mPackagesExemptFromImpactLevelThreshold.isEmpty()) {
- String packageNames = SystemProperties.get(PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD,
- DEFAULT_PACKAGES_EXEMPT_FROM_IMPACT_LEVEL_THRESHOLD);
- return Set.of(packageNames.split("\\s*,\\s*"));
- }
- return mPackagesExemptFromImpactLevelThreshold;
- }
-
- /**
- * Indicates that a mitigation was successfully triggered or executed during
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
- * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
- */
- public static final int MITIGATION_RESULT_SUCCESS =
- ObserverMitigationResult.MITIGATION_RESULT_SUCCESS;
-
- /**
- * Indicates that a mitigation executed during
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} or
- * {@link PackageHealthObserver#onExecuteBootLoopMitigation} was skipped.
- */
- public static final int MITIGATION_RESULT_SKIPPED =
- ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
-
-
- /**
- * Possible return values of the for mitigations executed during
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation} and
- * {@link PackageHealthObserver#onExecuteBootLoopMitigation}.
- * @hide
- */
- @Retention(SOURCE)
- @IntDef(prefix = "MITIGATION_RESULT_", value = {
- ObserverMitigationResult.MITIGATION_RESULT_SUCCESS,
- ObserverMitigationResult.MITIGATION_RESULT_SKIPPED,
- })
- public @interface ObserverMitigationResult {
- int MITIGATION_RESULT_SUCCESS = 1;
- int MITIGATION_RESULT_SKIPPED = 2;
- }
-
- /**
- * The minimum value that can be returned by any observer.
- * It represents that no mitigations were available.
- */
- public static final int USER_IMPACT_THRESHOLD_NONE =
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
-
- /**
- * The mitigation impact beyond which the user will start noticing the mitigations.
- */
- public static final int USER_IMPACT_THRESHOLD_MEDIUM =
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_20;
-
- /**
- * The mitigation impact beyond which the user impact is severely high.
- */
- public static final int USER_IMPACT_THRESHOLD_HIGH =
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
-
- /**
- * Possible severity values of the user impact of a
- * {@link PackageHealthObserver#onExecuteHealthCheckMitigation}.
- * @hide
- */
- @Retention(SOURCE)
- @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_10,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_20,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_40,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_50,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_71,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_75,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_80,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
- PackageHealthObserverImpact.USER_IMPACT_LEVEL_100})
- public @interface PackageHealthObserverImpact {
- /** No action to take. */
- int USER_IMPACT_LEVEL_0 = 0;
- /* Action has low user impact, user of a device will barely notice. */
- int USER_IMPACT_LEVEL_10 = 10;
- /* Actions having medium user impact, user of a device will likely notice. */
- int USER_IMPACT_LEVEL_20 = 20;
- int USER_IMPACT_LEVEL_30 = 30;
- int USER_IMPACT_LEVEL_40 = 40;
- int USER_IMPACT_LEVEL_50 = 50;
- int USER_IMPACT_LEVEL_70 = 70;
- /* Action has high user impact, a last resort, user of a device will be very frustrated. */
- int USER_IMPACT_LEVEL_71 = 71;
- int USER_IMPACT_LEVEL_75 = 75;
- int USER_IMPACT_LEVEL_80 = 80;
- int USER_IMPACT_LEVEL_90 = 90;
- int USER_IMPACT_LEVEL_100 = 100;
- }
-
- /** Register instances of this interface to receive notifications on package failure. */
- @SuppressLint({"CallbackName"})
- public interface PackageHealthObserver {
- /**
- * Called when health check fails for the {@code versionedPackage}.
- * Note: if the returned user impact is higher than {@link #USER_IMPACT_THRESHOLD_HIGH},
- * then {@link #onExecuteHealthCheckMitigation} would be called only in severe device
- * conditions like boot-loop or network failure.
- *
- * @param versionedPackage the package that is failing. This may be null if a native
- * service is crashing.
- * @param failureReason the type of failure that is occurring.
- * @param mitigationCount the number of times mitigation has been called for this package
- * (including this time).
- *
- * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
- * the impact of mitigation on the user in {@link #onExecuteHealthCheckMitigation}.
- * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
- */
- @PackageHealthObserverImpact int onHealthCheckFailed(
- @Nullable VersionedPackage versionedPackage,
- @FailureReasons int failureReason,
- int mitigationCount);
-
- /**
- * This would be called after {@link #onHealthCheckFailed}.
- * This is called only if current observer returned least impact mitigation for failed
- * health check.
- *
- * @param versionedPackage the package that is failing. This may be null if a native
- * service is crashing.
- * @param failureReason the type of failure that is occurring.
- * @param mitigationCount the number of times mitigation has been called for this package
- * (including this time).
- * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
- * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
- */
- @ObserverMitigationResult int onExecuteHealthCheckMitigation(
- @Nullable VersionedPackage versionedPackage,
- @FailureReasons int failureReason, int mitigationCount);
-
-
- /**
- * Called when the system server has booted several times within a window of time, defined
- * by {@link #mBootThreshold}
- *
- * @param mitigationCount the number of times mitigation has been attempted for this
- * boot loop (including this time).
- *
- * @return any value greater than {@link #USER_IMPACT_THRESHOLD_NONE} to express
- * the impact of mitigation on the user in {@link #onExecuteBootLoopMitigation}.
- * Returning {@link #USER_IMPACT_THRESHOLD_NONE} would indicate no mitigations available.
- */
- default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) {
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
-
- /**
- * This would be called after {@link #onBootLoop}.
- * This is called only if current observer returned least impact mitigation for fixing
- * boot loop.
- *
- * @param mitigationCount the number of times mitigation has been attempted for this
- * boot loop (including this time).
- *
- * @return {@link #MITIGATION_RESULT_SUCCESS} if the mitigation was successful,
- * or {@link #MITIGATION_RESULT_SKIPPED} if the mitigation was skipped.
- */
- default @ObserverMitigationResult int onExecuteBootLoopMitigation(int mitigationCount) {
- return ObserverMitigationResult.MITIGATION_RESULT_SKIPPED;
- }
-
- // TODO(b/120598832): Ensure uniqueness?
- /**
- * Identifier for the observer, should not change across device updates otherwise the
- * watchdog may drop observing packages with the old name.
- */
- @NonNull String getUniqueIdentifier();
-
- /**
- * An observer will not be pruned if this is set, even if the observer is not explicitly
- * monitoring any packages.
- */
- default boolean isPersistent() {
- return false;
- }
-
- /**
- * Returns {@code true} if this observer wishes to observe the given package, {@code false}
- * otherwise.
- * Any failing package can be passed on to the observer. Currently the packages that have
- * ANRs and perform {@link android.service.watchdog.ExplicitHealthCheckService} are being
- * passed to observers in these API.
- *
- * <p> A persistent observer may choose to start observing certain failing packages, even if
- * it has not explicitly asked to watch the package with {@link #startExplicitHealthCheck}.
- */
- default boolean mayObservePackage(@NonNull String packageName) {
- return false;
- }
- }
-
- @VisibleForTesting
- long getTriggerFailureCount() {
- synchronized (sLock) {
- return mTriggerFailureCount;
- }
- }
-
- @VisibleForTesting
- long getTriggerFailureDurationMs() {
- synchronized (sLock) {
- return mTriggerFailureDurationMs;
- }
- }
-
- /**
- * Serializes and syncs health check requests with the {@link ExplicitHealthCheckController}.
- */
- private void syncRequestsAsync() {
- mShortTaskHandler.removeCallbacks(mSyncRequests);
- mShortTaskHandler.post(mSyncRequests);
- }
-
- /**
- * Syncs health check requests with the {@link ExplicitHealthCheckController}.
- * Calls to this must be serialized.
- *
- * @see #syncRequestsAsync
- */
- private void syncRequests() {
- boolean syncRequired = false;
- synchronized (sLock) {
- if (mIsPackagesReady) {
- Set<String> packages = getPackagesPendingHealthChecksLocked();
- if (mSyncRequired || !packages.equals(mRequestedHealthCheckPackages)
- || packages.isEmpty()) {
- syncRequired = true;
- mRequestedHealthCheckPackages = packages;
- }
- } // else, we will sync requests when packages become ready
- }
-
- // Call outside lock to avoid holding lock when calling into the controller.
- if (syncRequired) {
- Slog.i(TAG, "Syncing health check requests for packages: "
- + mRequestedHealthCheckPackages);
- mHealthCheckController.syncRequests(mRequestedHealthCheckPackages);
- mSyncRequired = false;
- }
- }
-
- /**
- * Updates the observers monitoring {@code packageName} that explicit health check has passed.
- *
- * <p> This update is strictly for registered observers at the time of the call
- * Observers that register after this signal will have no knowledge of prior signals and will
- * effectively behave as if the explicit health check hasn't passed for {@code packageName}.
- *
- * <p> {@code packageName} can still be considered failed if reported by
- * {@link #notifyPackageFailureLocked} before the package expires.
- *
- * <p> Triggered by components outside the system server when they are fully functional after an
- * update.
- */
- private void onHealthCheckPassed(String packageName) {
- Slog.i(TAG, "Health check passed for package: " + packageName);
- boolean isStateChanged = false;
-
- synchronized (sLock) {
- for (int observerIdx = 0; observerIdx < mAllObservers.size(); observerIdx++) {
- ObserverInternal observer = mAllObservers.valueAt(observerIdx);
- MonitoredPackage monitoredPackage = observer.getMonitoredPackage(packageName);
-
- if (monitoredPackage != null) {
- int oldState = monitoredPackage.getHealthCheckStateLocked();
- int newState = monitoredPackage.tryPassHealthCheckLocked();
- isStateChanged |= oldState != newState;
- }
- }
- }
-
- if (isStateChanged) {
- syncState("health check passed for " + packageName);
- }
- }
-
- private void onSupportedPackages(List<PackageConfig> supportedPackages) {
- boolean isStateChanged = false;
-
- Map<String, Long> supportedPackageTimeouts = new ArrayMap<>();
- Iterator<PackageConfig> it = supportedPackages.iterator();
- while (it.hasNext()) {
- PackageConfig info = it.next();
- supportedPackageTimeouts.put(info.getPackageName(), info.getHealthCheckTimeoutMillis());
- }
-
- synchronized (sLock) {
- Slog.d(TAG, "Received supported packages " + supportedPackages);
- Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
- while (oit.hasNext()) {
- Iterator<MonitoredPackage> pit = oit.next().getMonitoredPackages()
- .values().iterator();
- while (pit.hasNext()) {
- MonitoredPackage monitoredPackage = pit.next();
- String packageName = monitoredPackage.getName();
- int oldState = monitoredPackage.getHealthCheckStateLocked();
- int newState;
-
- if (supportedPackageTimeouts.containsKey(packageName)) {
- // Supported packages become ACTIVE if currently INACTIVE
- newState = monitoredPackage.setHealthCheckActiveLocked(
- supportedPackageTimeouts.get(packageName));
- } else {
- // Unsupported packages are marked as PASSED unless already FAILED
- newState = monitoredPackage.tryPassHealthCheckLocked();
- }
- isStateChanged |= oldState != newState;
- }
- }
- }
-
- if (isStateChanged) {
- syncState("updated health check supported packages " + supportedPackages);
- }
- }
-
- private void onSyncRequestNotified() {
- synchronized (sLock) {
- mSyncRequired = true;
- syncRequestsAsync();
- }
- }
-
- @GuardedBy("sLock")
- private Set<String> getPackagesPendingHealthChecksLocked() {
- Set<String> packages = new ArraySet<>();
- Iterator<ObserverInternal> oit = mAllObservers.values().iterator();
- while (oit.hasNext()) {
- ObserverInternal observer = oit.next();
- Iterator<MonitoredPackage> pit =
- observer.getMonitoredPackages().values().iterator();
- while (pit.hasNext()) {
- MonitoredPackage monitoredPackage = pit.next();
- String packageName = monitoredPackage.getName();
- if (monitoredPackage.isPendingHealthChecksLocked()) {
- packages.add(packageName);
- }
- }
- }
- return packages;
- }
-
- /**
- * Syncs the state of the observers.
- *
- * <p> Prunes all observers, saves new state to disk, syncs health check requests with the
- * health check service and schedules the next state sync.
- */
- private void syncState(String reason) {
- synchronized (sLock) {
- Slog.i(TAG, "Syncing state, reason: " + reason);
- pruneObserversLocked();
-
- saveToFileAsync();
- syncRequestsAsync();
-
- // Done syncing state, schedule the next state sync
- scheduleNextSyncStateLocked();
- }
- }
-
- private void syncStateWithScheduledReason() {
- syncState("scheduled");
- }
-
- @GuardedBy("sLock")
- private void scheduleNextSyncStateLocked() {
- long durationMs = getNextStateSyncMillisLocked();
- mShortTaskHandler.removeCallbacks(mSyncStateWithScheduledReason);
- if (durationMs == Long.MAX_VALUE) {
- Slog.i(TAG, "Cancelling state sync, nothing to sync");
- mUptimeAtLastStateSync = 0;
- } else {
- mUptimeAtLastStateSync = mSystemClock.uptimeMillis();
- mShortTaskHandler.postDelayed(mSyncStateWithScheduledReason, durationMs);
- }
- }
-
- /**
- * Returns the next duration in millis to sync the watchdog state.
- *
- * @returns Long#MAX_VALUE if there are no observed packages.
- */
- @GuardedBy("sLock")
- private long getNextStateSyncMillisLocked() {
- long shortestDurationMs = Long.MAX_VALUE;
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- ArrayMap<String, MonitoredPackage> packages = mAllObservers.valueAt(oIndex)
- .getMonitoredPackages();
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- MonitoredPackage mp = packages.valueAt(pIndex);
- long duration = mp.getShortestScheduleDurationMsLocked();
- if (duration < shortestDurationMs) {
- shortestDurationMs = duration;
- }
- }
- }
- return shortestDurationMs;
- }
-
- /**
- * Removes {@code elapsedMs} milliseconds from all durations on monitored packages
- * and updates other internal state.
- */
- @GuardedBy("sLock")
- private void pruneObserversLocked() {
- long elapsedMs = mUptimeAtLastStateSync == 0
- ? 0 : mSystemClock.uptimeMillis() - mUptimeAtLastStateSync;
- if (elapsedMs <= 0) {
- Slog.i(TAG, "Not pruning observers, elapsed time: " + elapsedMs + "ms");
- return;
- }
-
- Iterator<ObserverInternal> it = mAllObservers.values().iterator();
- while (it.hasNext()) {
- ObserverInternal observer = it.next();
- Set<MonitoredPackage> failedPackages =
- observer.prunePackagesLocked(elapsedMs);
- if (!failedPackages.isEmpty()) {
- onHealthCheckFailed(observer, failedPackages);
- }
- if (observer.getMonitoredPackages().isEmpty() && (observer.registeredObserver == null
- || !observer.registeredObserver.isPersistent())) {
- Slog.i(TAG, "Discarding observer " + observer.name + ". All packages expired");
- it.remove();
- }
- }
- }
-
- private void onHealthCheckFailed(ObserverInternal observer,
- Set<MonitoredPackage> failedPackages) {
- mLongTaskHandler.post(() -> {
- synchronized (sLock) {
- PackageHealthObserver registeredObserver = observer.registeredObserver;
- if (registeredObserver != null) {
- Iterator<MonitoredPackage> it = failedPackages.iterator();
- while (it.hasNext()) {
- VersionedPackage versionedPkg = getVersionedPackage(it.next().getName());
- if (versionedPkg != null) {
- Slog.i(TAG,
- "Explicit health check failed for package " + versionedPkg);
- observer.observerExecutor.execute(() ->
- registeredObserver.onExecuteHealthCheckMitigation(versionedPkg,
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK,
- 1));
- }
- }
- }
- }
- });
- }
-
- /**
- * Gets PackageInfo for the given package. Matches any user and apex.
- *
- * @throws PackageManager.NameNotFoundException if no such package is installed.
- */
- private PackageInfo getPackageInfo(String packageName)
- throws PackageManager.NameNotFoundException {
- PackageManager pm = mContext.getPackageManager();
- try {
- // The MATCH_ANY_USER flag doesn't mix well with the MATCH_APEX
- // flag, so make two separate attempts to get the package info.
- // We don't need both flags at the same time because we assume
- // apex files are always installed for all users.
- return pm.getPackageInfo(packageName, PackageManager.MATCH_ANY_USER);
- } catch (PackageManager.NameNotFoundException e) {
- return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
- }
- }
-
- @Nullable
- private VersionedPackage getVersionedPackage(String packageName) {
- final PackageManager pm = mContext.getPackageManager();
- if (pm == null || TextUtils.isEmpty(packageName)) {
- return null;
- }
- try {
- final long versionCode = getPackageInfo(packageName).getLongVersionCode();
- return new VersionedPackage(packageName, versionCode);
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- }
-
- /**
- * Loads mAllObservers from file.
- *
- * <p>Note that this is <b>not</b> thread safe and should only called be called
- * from the constructor.
- */
- private void loadFromFile() {
- InputStream infile = null;
- mAllObservers.clear();
- try {
- infile = mPolicyFile.openRead();
- final XmlPullParser parser = Xml.newPullParser();
- parser.setInput(infile, UTF_8.name());
- XmlUtils.beginDocument(parser, TAG_PACKAGE_WATCHDOG);
- int outerDepth = parser.getDepth();
- while (XmlUtils.nextElementWithin(parser, outerDepth)) {
- ObserverInternal observer = ObserverInternal.read(parser, this);
- if (observer != null) {
- mAllObservers.put(observer.name, observer);
- }
- }
- } catch (FileNotFoundException e) {
- // Nothing to monitor
- } catch (Exception e) {
- Slog.wtf(TAG, "Unable to read monitored packages, deleting file", e);
- mPolicyFile.delete();
- } finally {
- IoUtils.closeQuietly(infile);
- }
- }
-
- private void onPropertyChanged(DeviceConfig.Properties properties) {
- try {
- updateConfigs();
- } catch (Exception ignore) {
- Slog.w(TAG, "Failed to reload device config changes");
- }
- }
-
- /** Adds a {@link DeviceConfig#OnPropertiesChangedListener}. */
- private void setPropertyChangedListenerLocked() {
- DeviceConfig.addOnPropertiesChangedListener(
- DeviceConfig.NAMESPACE_ROLLBACK,
- mContext.getMainExecutor(),
- mOnPropertyChangedListener);
- }
-
- @VisibleForTesting
- void removePropertyChangedListener() {
- DeviceConfig.removeOnPropertiesChangedListener(mOnPropertyChangedListener);
- }
-
- /**
- * Health check is enabled or disabled after reading the flags
- * from DeviceConfig.
- */
- @VisibleForTesting
- void updateConfigs() {
- synchronized (sLock) {
- mTriggerFailureCount = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
- DEFAULT_TRIGGER_FAILURE_COUNT);
- if (mTriggerFailureCount <= 0) {
- mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
- }
-
- mTriggerFailureDurationMs = DeviceConfig.getInt(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
- DEFAULT_TRIGGER_FAILURE_DURATION_MS);
- if (mTriggerFailureDurationMs <= 0) {
- mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
- }
-
- setExplicitHealthCheckEnabled(DeviceConfig.getBoolean(
- DeviceConfig.NAMESPACE_ROLLBACK,
- PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
- DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED));
- }
- }
-
- /**
- * Persists mAllObservers to file. Threshold information is ignored.
- */
- private boolean saveToFile() {
- Slog.i(TAG, "Saving observer state to file");
- synchronized (sLock) {
- FileOutputStream stream;
- try {
- stream = mPolicyFile.startWrite();
- } catch (IOException e) {
- Slog.w(TAG, "Cannot update monitored packages", e);
- return false;
- }
-
- try {
- XmlSerializer out = new FastXmlSerializer();
- out.setOutput(stream, UTF_8.name());
- out.startDocument(null, true);
- out.startTag(null, TAG_PACKAGE_WATCHDOG);
- out.attribute(null, ATTR_VERSION, Integer.toString(DB_VERSION));
- for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
- mAllObservers.valueAt(oIndex).writeLocked(out);
- }
- out.endTag(null, TAG_PACKAGE_WATCHDOG);
- out.endDocument();
- mPolicyFile.finishWrite(stream);
- return true;
- } catch (IOException e) {
- Slog.w(TAG, "Failed to save monitored packages, restoring backup", e);
- mPolicyFile.failWrite(stream);
- return false;
- }
- }
- }
-
- private void saveToFileAsync() {
- if (!mLongTaskHandler.hasCallbacks(mSaveToFile)) {
- mLongTaskHandler.post(mSaveToFile);
- }
- }
-
- /** @hide Convert a {@code LongArrayQueue} to a String of comma-separated values. */
- public static String longArrayQueueToString(LongArrayQueue queue) {
- if (queue.size() > 0) {
- StringBuilder sb = new StringBuilder();
- sb.append(queue.get(0));
- for (int i = 1; i < queue.size(); i++) {
- sb.append(",");
- sb.append(queue.get(i));
- }
- return sb.toString();
- }
- return "";
- }
-
- /** @hide Parse a comma-separated String of longs into a LongArrayQueue. */
- public static LongArrayQueue parseLongArrayQueue(String commaSeparatedValues) {
- LongArrayQueue result = new LongArrayQueue();
- if (!TextUtils.isEmpty(commaSeparatedValues)) {
- String[] values = commaSeparatedValues.split(",");
- for (String value : values) {
- result.addLast(Long.parseLong(value));
- }
- }
- return result;
- }
-
-
- /** Dump status of every observer in mAllObservers. */
- public void dump(@NonNull PrintWriter pw) {
- if (Flags.synchronousRebootInRescueParty() && isRecoveryTriggeredReboot()) {
- dumpInternal(pw);
- } else {
- synchronized (sLock) {
- dumpInternal(pw);
- }
- }
- }
-
- /**
- * Check if we're currently attempting to reboot during mitigation. This method must return
- * true if triggered reboot early during a boot loop, since the device will not be fully booted
- * at this time.
- */
- public static boolean isRecoveryTriggeredReboot() {
- return isFactoryResetPropertySet() || isRebootPropertySet();
- }
-
- private static boolean isFactoryResetPropertySet() {
- return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
- }
-
- private static boolean isRebootPropertySet() {
- return CrashRecoveryProperties.attemptingReboot().orElse(false);
- }
-
- private void dumpInternal(@NonNull PrintWriter pw) {
- IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
- ipw.println("Package Watchdog status");
- ipw.increaseIndent();
- synchronized (sLock) {
- for (String observerName : mAllObservers.keySet()) {
- ipw.println("Observer name: " + observerName);
- ipw.increaseIndent();
- ObserverInternal observerInternal = mAllObservers.get(observerName);
- observerInternal.dump(ipw);
- ipw.decreaseIndent();
- }
- }
- ipw.decreaseIndent();
- dumpCrashRecoveryEvents(ipw);
- }
-
- @VisibleForTesting
- @GuardedBy("sLock")
- void registerObserverInternal(ObserverInternal observerInternal) {
- mAllObservers.put(observerInternal.name, observerInternal);
- }
-
- /**
- * Represents an observer monitoring a set of packages along with the failure thresholds for
- * each package.
- *
- * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
- * instances of this class.
- */
- static class ObserverInternal {
- public final String name;
- @GuardedBy("sLock")
- private final ArrayMap<String, MonitoredPackage> mPackages = new ArrayMap<>();
- @Nullable
- @GuardedBy("sLock")
- public PackageHealthObserver registeredObserver;
- public Executor observerExecutor;
- private int mMitigationCount;
-
- ObserverInternal(String name, List<MonitoredPackage> packages) {
- this(name, packages, /*mitigationCount=*/ 0);
- }
-
- ObserverInternal(String name, List<MonitoredPackage> packages, int mitigationCount) {
- this.name = name;
- updatePackagesLocked(packages);
- this.mMitigationCount = mitigationCount;
- }
-
- /**
- * Writes important {@link MonitoredPackage} details for this observer to file.
- * Does not persist any package failure thresholds.
- */
- @GuardedBy("sLock")
- public boolean writeLocked(XmlSerializer out) {
- try {
- out.startTag(null, TAG_OBSERVER);
- out.attribute(null, ATTR_NAME, name);
- if (Flags.recoverabilityDetection()) {
- out.attribute(null, ATTR_MITIGATION_COUNT, Integer.toString(mMitigationCount));
- }
- for (int i = 0; i < mPackages.size(); i++) {
- MonitoredPackage p = mPackages.valueAt(i);
- p.writeLocked(out);
- }
- out.endTag(null, TAG_OBSERVER);
- return true;
- } catch (IOException e) {
- Slog.w(TAG, "Cannot save observer", e);
- return false;
- }
- }
-
- public int getBootMitigationCount() {
- return mMitigationCount;
- }
-
- public void setBootMitigationCount(int mitigationCount) {
- mMitigationCount = mitigationCount;
- }
-
- @GuardedBy("sLock")
- public void updatePackagesLocked(List<MonitoredPackage> packages) {
- for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
- MonitoredPackage p = packages.get(pIndex);
- MonitoredPackage existingPackage = getMonitoredPackage(p.getName());
- if (existingPackage != null) {
- existingPackage.updateHealthCheckDuration(p.mDurationMs);
- } else {
- putMonitoredPackage(p);
- }
- }
- }
-
- /**
- * Reduces the monitoring durations of all packages observed by this observer by
- * {@code elapsedMs}. If any duration is less than 0, the package is removed from
- * observation. If any health check duration is less than 0, the health check result
- * is evaluated.
- *
- * @return a {@link Set} of packages that were removed from the observer without explicit
- * health check passing, or an empty list if no package expired for which an explicit health
- * check was still pending
- */
- @GuardedBy("sLock")
- private Set<MonitoredPackage> prunePackagesLocked(long elapsedMs) {
- Set<MonitoredPackage> failedPackages = new ArraySet<>();
- Iterator<MonitoredPackage> it = mPackages.values().iterator();
- while (it.hasNext()) {
- MonitoredPackage p = it.next();
- int oldState = p.getHealthCheckStateLocked();
- int newState = p.handleElapsedTimeLocked(elapsedMs);
- if (oldState != HealthCheckState.FAILED
- && newState == HealthCheckState.FAILED) {
- Slog.i(TAG, "Package " + p.getName() + " failed health check");
- failedPackages.add(p);
- }
- if (p.isExpiredLocked()) {
- it.remove();
- }
- }
- return failedPackages;
- }
-
- /**
- * Increments failure counts of {@code packageName}.
- * @returns {@code true} if failure threshold is exceeded, {@code false} otherwise
- * @hide
- */
- @GuardedBy("sLock")
- public boolean notifyPackageFailureLocked(String packageName) {
- if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
- && registeredObserver.mayObservePackage(packageName)) {
- putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
- packageName, DEFAULT_OBSERVING_DURATION_MS, false));
- }
- MonitoredPackage p = getMonitoredPackage(packageName);
- if (p != null) {
- return p.onFailureLocked();
- }
- return false;
- }
-
- /**
- * Returns the map of packages monitored by this observer.
- *
- * @return a mapping of package names to {@link MonitoredPackage} objects.
- */
- @GuardedBy("sLock")
- public ArrayMap<String, MonitoredPackage> getMonitoredPackages() {
- return mPackages;
- }
-
- /**
- * Returns the {@link MonitoredPackage} associated with a given package name if the
- * package is being monitored by this observer.
- *
- * @param packageName: the name of the package.
- * @return the {@link MonitoredPackage} object associated with the package name if one
- * exists, {@code null} otherwise.
- */
- @GuardedBy("sLock")
- @Nullable
- public MonitoredPackage getMonitoredPackage(String packageName) {
- return mPackages.get(packageName);
- }
-
- /**
- * Associates a {@link MonitoredPackage} with the observer.
- *
- * @param p: the {@link MonitoredPackage} to store.
- */
- @GuardedBy("sLock")
- public void putMonitoredPackage(MonitoredPackage p) {
- mPackages.put(p.getName(), p);
- }
-
- /**
- * Returns one ObserverInternal from the {@code parser} and advances its state.
- *
- * <p>Note that this method is <b>not</b> thread safe. It should only be called from
- * #loadFromFile which in turn is only called on construction of the
- * singleton PackageWatchdog.
- **/
- public static ObserverInternal read(XmlPullParser parser, PackageWatchdog watchdog) {
- String observerName = null;
- int observerMitigationCount = 0;
- if (TAG_OBSERVER.equals(parser.getName())) {
- observerName = parser.getAttributeValue(null, ATTR_NAME);
- if (TextUtils.isEmpty(observerName)) {
- Slog.wtf(TAG, "Unable to read observer name");
- return null;
- }
- }
- List<MonitoredPackage> packages = new ArrayList<>();
- int innerDepth = parser.getDepth();
- try {
- if (Flags.recoverabilityDetection()) {
- try {
- observerMitigationCount = Integer.parseInt(
- parser.getAttributeValue(null, ATTR_MITIGATION_COUNT));
- } catch (Exception e) {
- Slog.i(
- TAG,
- "ObserverInternal mitigation count was not present.");
- }
- }
- while (XmlUtils.nextElementWithin(parser, innerDepth)) {
- if (TAG_PACKAGE.equals(parser.getName())) {
- try {
- MonitoredPackage pkg = watchdog.parseMonitoredPackage(parser);
- if (pkg != null) {
- packages.add(pkg);
- }
- } catch (NumberFormatException e) {
- Slog.wtf(TAG, "Skipping package for observer " + observerName, e);
- continue;
- }
- }
- }
- } catch (XmlPullParserException | IOException e) {
- Slog.wtf(TAG, "Unable to read observer " + observerName, e);
- return null;
- }
- if (packages.isEmpty()) {
- return null;
- }
- return new ObserverInternal(observerName, packages, observerMitigationCount);
- }
-
- /** Dumps information about this observer and the packages it watches. */
- public void dump(IndentingPrintWriter pw) {
- boolean isPersistent = registeredObserver != null && registeredObserver.isPersistent();
- pw.println("Persistent: " + isPersistent);
- for (String packageName : mPackages.keySet()) {
- MonitoredPackage p = getMonitoredPackage(packageName);
- pw.println(packageName + ": ");
- pw.increaseIndent();
- pw.println("# Failures: " + p.mFailureHistory.size());
- pw.println("Monitoring duration remaining: " + p.mDurationMs + "ms");
- pw.println("Explicit health check duration: " + p.mHealthCheckDurationMs + "ms");
- pw.println("Health check state: " + p.toString(p.mHealthCheckState));
- pw.decreaseIndent();
- }
- }
- }
-
- /** @hide */
- @Retention(SOURCE)
- @IntDef(value = {
- HealthCheckState.ACTIVE,
- HealthCheckState.INACTIVE,
- HealthCheckState.PASSED,
- HealthCheckState.FAILED})
- public @interface HealthCheckState {
- // The package has not passed health check but has requested a health check
- int ACTIVE = 0;
- // The package has not passed health check and has not requested a health check
- int INACTIVE = 1;
- // The package has passed health check
- int PASSED = 2;
- // The package has failed health check
- int FAILED = 3;
- }
-
- MonitoredPackage newMonitoredPackage(
- String name, long durationMs, boolean hasPassedHealthCheck) {
- return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck,
- new LongArrayQueue());
- }
-
- MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
- boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {
- return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
- hasPassedHealthCheck, mitigationCalls);
- }
-
- MonitoredPackage parseMonitoredPackage(XmlPullParser parser)
- throws XmlPullParserException {
- String packageName = parser.getAttributeValue(null, ATTR_NAME);
- long duration = Long.parseLong(parser.getAttributeValue(null, ATTR_DURATION));
- long healthCheckDuration = Long.parseLong(parser.getAttributeValue(null,
- ATTR_EXPLICIT_HEALTH_CHECK_DURATION));
- boolean hasPassedHealthCheck = Boolean.parseBoolean(parser.getAttributeValue(null,
- ATTR_PASSED_HEALTH_CHECK));
- LongArrayQueue mitigationCalls = parseLongArrayQueue(
- parser.getAttributeValue(null, ATTR_MITIGATION_CALLS));
- return newMonitoredPackage(packageName,
- duration, healthCheckDuration, hasPassedHealthCheck, mitigationCalls);
- }
-
- /**
- * Represents a package and its health check state along with the time
- * it should be monitored for.
- *
- * <p> Note, the PackageWatchdog#sLock must always be held when reading or writing
- * instances of this class.
- */
- class MonitoredPackage {
- private final String mPackageName;
- // Times when package failures happen sorted in ascending order
- @GuardedBy("sLock")
- private final LongArrayQueue mFailureHistory = new LongArrayQueue();
- // Times when an observer was called to mitigate this package's failure. Sorted in
- // ascending order.
- @GuardedBy("sLock")
- private final LongArrayQueue mMitigationCalls;
- // One of STATE_[ACTIVE|INACTIVE|PASSED|FAILED]. Updated on construction and after
- // methods that could change the health check state: handleElapsedTimeLocked and
- // tryPassHealthCheckLocked
- private int mHealthCheckState = HealthCheckState.INACTIVE;
- // Whether an explicit health check has passed.
- // This value in addition with mHealthCheckDurationMs determines the health check state
- // of the package, see #getHealthCheckStateLocked
- @GuardedBy("sLock")
- private boolean mHasPassedHealthCheck;
- // System uptime duration to monitor package.
- @GuardedBy("sLock")
- private long mDurationMs;
- // System uptime duration to check the result of an explicit health check
- // Initially, MAX_VALUE until we get a value from the health check service
- // and request health checks.
- // This value in addition with mHasPassedHealthCheck determines the health check state
- // of the package, see #getHealthCheckStateLocked
- @GuardedBy("sLock")
- private long mHealthCheckDurationMs = Long.MAX_VALUE;
-
- MonitoredPackage(String packageName, long durationMs,
- long healthCheckDurationMs, boolean hasPassedHealthCheck,
- LongArrayQueue mitigationCalls) {
- mPackageName = packageName;
- mDurationMs = durationMs;
- mHealthCheckDurationMs = healthCheckDurationMs;
- mHasPassedHealthCheck = hasPassedHealthCheck;
- mMitigationCalls = mitigationCalls;
- updateHealthCheckStateLocked();
- }
-
- /** Writes the salient fields to disk using {@code out}.
- * @hide
- */
- @GuardedBy("sLock")
- public void writeLocked(XmlSerializer out) throws IOException {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATTR_NAME, getName());
- out.attribute(null, ATTR_DURATION, Long.toString(mDurationMs));
- out.attribute(null, ATTR_EXPLICIT_HEALTH_CHECK_DURATION,
- Long.toString(mHealthCheckDurationMs));
- out.attribute(null, ATTR_PASSED_HEALTH_CHECK, Boolean.toString(mHasPassedHealthCheck));
- LongArrayQueue normalizedCalls = normalizeMitigationCalls();
- out.attribute(null, ATTR_MITIGATION_CALLS, longArrayQueueToString(normalizedCalls));
- out.endTag(null, TAG_PACKAGE);
- }
-
- /**
- * Increment package failures or resets failure count depending on the last package failure.
- *
- * @return {@code true} if failure count exceeds a threshold, {@code false} otherwise
- */
- @GuardedBy("sLock")
- public boolean onFailureLocked() {
- // Sliding window algorithm: find out if there exists a window containing failures >=
- // mTriggerFailureCount.
- final long now = mSystemClock.uptimeMillis();
- mFailureHistory.addLast(now);
- while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
- // Prune values falling out of the window
- mFailureHistory.removeFirst();
- }
- boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
- if (failed) {
- mFailureHistory.clear();
- }
- return failed;
- }
-
- /**
- * Notes the timestamp of a mitigation call into the observer.
- */
- @GuardedBy("sLock")
- public void noteMitigationCallLocked() {
- mMitigationCalls.addLast(mSystemClock.uptimeMillis());
- }
-
- /**
- * Prunes any mitigation calls outside of the de-escalation window, and returns the
- * number of calls that are in the window afterwards.
- *
- * @return the number of mitigation calls made in the de-escalation window.
- */
- @GuardedBy("sLock")
- public int getMitigationCountLocked() {
- try {
- final long now = mSystemClock.uptimeMillis();
- while (now - mMitigationCalls.peekFirst() > DEFAULT_DEESCALATION_WINDOW_MS) {
- mMitigationCalls.removeFirst();
- }
- } catch (NoSuchElementException ignore) {
- }
-
- return mMitigationCalls.size();
- }
-
- /**
- * Before writing to disk, make the mitigation call timestamps relative to the current
- * system uptime. This is because they need to be relative to the uptime which will reset
- * at the next boot.
- *
- * @return a LongArrayQueue of the mitigation calls relative to the current system uptime.
- */
- @GuardedBy("sLock")
- public LongArrayQueue normalizeMitigationCalls() {
- LongArrayQueue normalized = new LongArrayQueue();
- final long now = mSystemClock.uptimeMillis();
- for (int i = 0; i < mMitigationCalls.size(); i++) {
- normalized.addLast(mMitigationCalls.get(i) - now);
- }
- return normalized;
- }
-
- /**
- * Sets the initial health check duration.
- *
- * @return the new health check state
- */
- @GuardedBy("sLock")
- public int setHealthCheckActiveLocked(long initialHealthCheckDurationMs) {
- if (initialHealthCheckDurationMs <= 0) {
- Slog.wtf(TAG, "Cannot set non-positive health check duration "
- + initialHealthCheckDurationMs + "ms for package " + getName()
- + ". Using total duration " + mDurationMs + "ms instead");
- initialHealthCheckDurationMs = mDurationMs;
- }
- if (mHealthCheckState == HealthCheckState.INACTIVE) {
- // Transitions to ACTIVE
- mHealthCheckDurationMs = initialHealthCheckDurationMs;
- }
- return updateHealthCheckStateLocked();
- }
-
- /**
- * Updates the monitoring durations of the package.
- *
- * @return the new health check state
- */
- @GuardedBy("sLock")
- public int handleElapsedTimeLocked(long elapsedMs) {
- if (elapsedMs <= 0) {
- Slog.w(TAG, "Cannot handle non-positive elapsed time for package " + getName());
- return mHealthCheckState;
- }
- // Transitions to FAILED if now <= 0 and health check not passed
- mDurationMs -= elapsedMs;
- if (mHealthCheckState == HealthCheckState.ACTIVE) {
- // We only update health check durations if we have #setHealthCheckActiveLocked
- // This ensures we don't leave the INACTIVE state for an unexpected elapsed time
- // Transitions to FAILED if now <= 0 and health check not passed
- mHealthCheckDurationMs -= elapsedMs;
- }
- return updateHealthCheckStateLocked();
- }
-
- /** Explicitly update the monitoring duration of the package. */
- @GuardedBy("sLock")
- public void updateHealthCheckDuration(long newDurationMs) {
- mDurationMs = newDurationMs;
- }
-
- /**
- * Marks the health check as passed and transitions to {@link HealthCheckState.PASSED}
- * if not yet {@link HealthCheckState.FAILED}.
- *
- * @return the new {@link HealthCheckState health check state}
- */
- @GuardedBy("sLock")
- @HealthCheckState
- public int tryPassHealthCheckLocked() {
- if (mHealthCheckState != HealthCheckState.FAILED) {
- // FAILED is a final state so only pass if we haven't failed
- // Transition to PASSED
- mHasPassedHealthCheck = true;
- }
- return updateHealthCheckStateLocked();
- }
-
- /** Returns the monitored package name. */
- private String getName() {
- return mPackageName;
- }
-
- /**
- * Returns the current {@link HealthCheckState health check state}.
- */
- @GuardedBy("sLock")
- @HealthCheckState
- public int getHealthCheckStateLocked() {
- return mHealthCheckState;
- }
-
- /**
- * Returns the shortest duration before the package should be scheduled for a prune.
- *
- * @return the duration or {@link Long#MAX_VALUE} if the package should not be scheduled
- */
- @GuardedBy("sLock")
- public long getShortestScheduleDurationMsLocked() {
- // Consider health check duration only if #isPendingHealthChecksLocked is true
- return Math.min(toPositive(mDurationMs),
- isPendingHealthChecksLocked()
- ? toPositive(mHealthCheckDurationMs) : Long.MAX_VALUE);
- }
-
- /**
- * Returns {@code true} if the total duration left to monitor the package is less than or
- * equal to 0 {@code false} otherwise.
- */
- @GuardedBy("sLock")
- public boolean isExpiredLocked() {
- return mDurationMs <= 0;
- }
-
- /**
- * Returns {@code true} if the package, {@link #getName} is expecting health check results
- * {@code false} otherwise.
- */
- @GuardedBy("sLock")
- public boolean isPendingHealthChecksLocked() {
- return mHealthCheckState == HealthCheckState.ACTIVE
- || mHealthCheckState == HealthCheckState.INACTIVE;
- }
-
- /**
- * Updates the health check state based on {@link #mHasPassedHealthCheck}
- * and {@link #mHealthCheckDurationMs}.
- *
- * @return the new {@link HealthCheckState health check state}
- */
- @GuardedBy("sLock")
- @HealthCheckState
- private int updateHealthCheckStateLocked() {
- int oldState = mHealthCheckState;
- if (mHasPassedHealthCheck) {
- // Set final state first to avoid ambiguity
- mHealthCheckState = HealthCheckState.PASSED;
- } else if (mHealthCheckDurationMs <= 0 || mDurationMs <= 0) {
- // Set final state first to avoid ambiguity
- mHealthCheckState = HealthCheckState.FAILED;
- } else if (mHealthCheckDurationMs == Long.MAX_VALUE) {
- mHealthCheckState = HealthCheckState.INACTIVE;
- } else {
- mHealthCheckState = HealthCheckState.ACTIVE;
- }
-
- if (oldState != mHealthCheckState) {
- Slog.i(TAG, "Updated health check state for package " + getName() + ": "
- + toString(oldState) + " -> " + toString(mHealthCheckState));
- }
- return mHealthCheckState;
- }
-
- /** Returns a {@link String} representation of the current health check state. */
- private String toString(@HealthCheckState int state) {
- switch (state) {
- case HealthCheckState.ACTIVE:
- return "ACTIVE";
- case HealthCheckState.INACTIVE:
- return "INACTIVE";
- case HealthCheckState.PASSED:
- return "PASSED";
- case HealthCheckState.FAILED:
- return "FAILED";
- default:
- return "UNKNOWN";
- }
- }
-
- /** Returns {@code value} if it is greater than 0 or {@link Long#MAX_VALUE} otherwise. */
- private long toPositive(long value) {
- return value > 0 ? value : Long.MAX_VALUE;
- }
-
- /** Compares the equality of this object with another {@link MonitoredPackage}. */
- @VisibleForTesting
- boolean isEqualTo(MonitoredPackage pkg) {
- return (getName().equals(pkg.getName()))
- && mDurationMs == pkg.mDurationMs
- && mHasPassedHealthCheck == pkg.mHasPassedHealthCheck
- && mHealthCheckDurationMs == pkg.mHealthCheckDurationMs
- && (mMitigationCalls.toString()).equals(pkg.mMitigationCalls.toString());
- }
- }
-
- @GuardedBy("sLock")
- @SuppressWarnings("GuardedBy")
- void saveAllObserversBootMitigationCountToMetadata(String filePath) {
- HashMap<String, Integer> bootMitigationCounts = new HashMap<>();
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- bootMitigationCounts.put(observer.name, observer.getBootMitigationCount());
- }
-
- FileOutputStream fileStream = null;
- ObjectOutputStream objectStream = null;
- try {
- fileStream = new FileOutputStream(new File(filePath));
- objectStream = new ObjectOutputStream(fileStream);
- objectStream.writeObject(bootMitigationCounts);
- objectStream.flush();
- } catch (Exception e) {
- Slog.i(TAG, "Could not save observers metadata to file: " + e);
- return;
- } finally {
- IoUtils.closeQuietly(objectStream);
- IoUtils.closeQuietly(fileStream);
- }
- }
-
- /**
- * Handles the thresholding logic for system server boots.
- */
- class BootThreshold {
-
- private final int mBootTriggerCount;
- private final long mTriggerWindow;
-
- BootThreshold(int bootTriggerCount, long triggerWindow) {
- this.mBootTriggerCount = bootTriggerCount;
- this.mTriggerWindow = triggerWindow;
- }
-
- public void reset() {
- setStart(0);
- setCount(0);
- }
-
- protected int getCount() {
- return CrashRecoveryProperties.rescueBootCount().orElse(0);
- }
-
- protected void setCount(int count) {
- CrashRecoveryProperties.rescueBootCount(count);
- }
-
- public long getStart() {
- return CrashRecoveryProperties.rescueBootStart().orElse(0L);
- }
-
- public int getMitigationCount() {
- return CrashRecoveryProperties.bootMitigationCount().orElse(0);
- }
-
- public void setStart(long start) {
- CrashRecoveryProperties.rescueBootStart(getStartTime(start));
- }
-
- public void setMitigationStart(long start) {
- CrashRecoveryProperties.bootMitigationStart(getStartTime(start));
- }
-
- public long getMitigationStart() {
- return CrashRecoveryProperties.bootMitigationStart().orElse(0L);
- }
-
- public void setMitigationCount(int count) {
- CrashRecoveryProperties.bootMitigationCount(count);
- }
-
- private static long constrain(long amount, long low, long high) {
- return amount < low ? low : (amount > high ? high : amount);
- }
-
- public long getStartTime(long start) {
- final long now = mSystemClock.uptimeMillis();
- return constrain(start, 0, now);
- }
-
- public void saveMitigationCountToMetadata() {
- try (BufferedWriter writer = new BufferedWriter(new FileWriter(METADATA_FILE))) {
- writer.write(String.valueOf(getMitigationCount()));
- } catch (Exception e) {
- Slog.e(TAG, "Could not save metadata to file: " + e);
- }
- }
-
- public void readMitigationCountFromMetadataIfNecessary() {
- File bootPropsFile = new File(METADATA_FILE);
- if (bootPropsFile.exists()) {
- try (BufferedReader reader = new BufferedReader(new FileReader(METADATA_FILE))) {
- String mitigationCount = reader.readLine();
- setMitigationCount(Integer.parseInt(mitigationCount));
- bootPropsFile.delete();
- } catch (Exception e) {
- Slog.i(TAG, "Could not read metadata file: " + e);
- }
- }
- }
-
-
- /** Increments the boot counter, and returns whether the device is bootlooping. */
- @GuardedBy("sLock")
- public boolean incrementAndTest() {
- if (Flags.recoverabilityDetection()) {
- readAllObserversBootMitigationCountIfNecessary(METADATA_FILE);
- } else {
- readMitigationCountFromMetadataIfNecessary();
- }
-
- final long now = mSystemClock.uptimeMillis();
- if (now - getStart() < 0) {
- Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
- setStart(now);
- setMitigationStart(now);
- }
- if (now - getMitigationStart() > DEFAULT_DEESCALATION_WINDOW_MS) {
- setMitigationStart(now);
- if (Flags.recoverabilityDetection()) {
- resetAllObserversBootMitigationCount();
- } else {
- setMitigationCount(0);
- }
- }
- final long window = now - getStart();
- if (window >= mTriggerWindow) {
- setCount(1);
- setStart(now);
- return false;
- } else {
- int count = getCount() + 1;
- setCount(count);
- EventLog.writeEvent(LOG_TAG_RESCUE_NOTE, Process.ROOT_UID, count, window);
- if (Flags.recoverabilityDetection()) {
- // After a reboot (e.g. by WARM_REBOOT or mainline rollback) we apply
- // mitigations without waiting for DEFAULT_BOOT_LOOP_TRIGGER_COUNT.
- return (count >= mBootTriggerCount)
- || (performedMitigationsDuringWindow() && count > 1);
- }
- return count >= mBootTriggerCount;
- }
- }
-
- @GuardedBy("sLock")
- private boolean performedMitigationsDuringWindow() {
- for (ObserverInternal observerInternal: mAllObservers.values()) {
- if (observerInternal.getBootMitigationCount() > 0) {
- return true;
- }
- }
- return false;
- }
-
- @GuardedBy("sLock")
- private void resetAllObserversBootMitigationCount() {
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- observer.setBootMitigationCount(0);
- }
- saveAllObserversBootMitigationCountToMetadata(METADATA_FILE);
- }
-
- @GuardedBy("sLock")
- @SuppressWarnings("GuardedBy")
- void readAllObserversBootMitigationCountIfNecessary(String filePath) {
- File metadataFile = new File(filePath);
- if (metadataFile.exists()) {
- FileInputStream fileStream = null;
- ObjectInputStream objectStream = null;
- HashMap<String, Integer> bootMitigationCounts = null;
- try {
- fileStream = new FileInputStream(metadataFile);
- objectStream = new ObjectInputStream(fileStream);
- bootMitigationCounts =
- (HashMap<String, Integer>) objectStream.readObject();
- } catch (Exception e) {
- Slog.i(TAG, "Could not read observer metadata file: " + e);
- return;
- } finally {
- IoUtils.closeQuietly(objectStream);
- IoUtils.closeQuietly(fileStream);
- }
-
- if (bootMitigationCounts == null || bootMitigationCounts.isEmpty()) {
- Slog.i(TAG, "No observer in metadata file");
- return;
- }
- for (int i = 0; i < mAllObservers.size(); i++) {
- final ObserverInternal observer = mAllObservers.valueAt(i);
- if (bootMitigationCounts.containsKey(observer.name)) {
- observer.setBootMitigationCount(
- bootMitigationCounts.get(observer.name));
- }
- }
- }
- }
- }
-
- /**
- * Register broadcast receiver for shutdown.
- * We would save the observer state to persist across boots.
- *
- * @hide
- */
- public void registerShutdownBroadcastReceiver() {
- BroadcastReceiver shutdownEventReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- // Only write if intent is relevant to device reboot or shutdown.
- String intentAction = intent.getAction();
- if (ACTION_REBOOT.equals(intentAction)
- || ACTION_SHUTDOWN.equals(intentAction)) {
- writeNow();
- }
- }
- };
-
- // Setup receiver for device reboots or shutdowns.
- IntentFilter filter = new IntentFilter(ACTION_REBOOT);
- filter.addAction(ACTION_SHUTDOWN);
- mContext.registerReceiverForAllUsers(shutdownEventReceiver, filter, null,
- /* run on main thread */ null);
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
deleted file mode 100644
index 846da194b3c3..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/RescueParty.java
+++ /dev/null
@@ -1,861 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.crashrecovery.flags.Flags;
-import android.os.Build;
-import android.os.PowerManager;
-import android.os.RecoverySystem;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.provider.Settings;
-import android.sysprop.CrashRecoveryProperties;
-import android.text.TextUtils;
-import android.util.EventLog;
-import android.util.FileUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.io.File;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utilities to help rescue the system from crash loops. Callers are expected to
- * report boot events and persistent app crashes, and if they happen frequently
- * enough this class will slowly escalate through several rescue operations
- * before finally rebooting and prompting the user if they want to wipe data as
- * a last resort.
- *
- * @hide
- */
-public class RescueParty {
- @VisibleForTesting
- static final String PROP_ENABLE_RESCUE = "persist.sys.enable_rescue";
- @VisibleForTesting
- static final int LEVEL_NONE = 0;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
- @VisibleForTesting
- static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
- @VisibleForTesting
- static final int LEVEL_WARM_REBOOT = 4;
- @VisibleForTesting
- static final int LEVEL_FACTORY_RESET = 5;
- @VisibleForTesting
- static final int RESCUE_LEVEL_NONE = 0;
- @VisibleForTesting
- static final int RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET = 1;
- @VisibleForTesting
- static final int RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET = 2;
- @VisibleForTesting
- static final int RESCUE_LEVEL_WARM_REBOOT = 3;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 4;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 5;
- @VisibleForTesting
- static final int RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 6;
- @VisibleForTesting
- static final int RESCUE_LEVEL_FACTORY_RESET = 7;
-
- @IntDef(prefix = { "RESCUE_LEVEL_" }, value = {
- RESCUE_LEVEL_NONE,
- RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET,
- RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET,
- RESCUE_LEVEL_WARM_REBOOT,
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
- RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
- RESCUE_LEVEL_FACTORY_RESET
- })
- @Retention(RetentionPolicy.SOURCE)
- @interface RescueLevels {}
-
- @VisibleForTesting
- static final String RESCUE_NON_REBOOT_LEVEL_LIMIT = "persist.sys.rescue_non_reboot_level_limit";
- @VisibleForTesting
- static final int DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT = RESCUE_LEVEL_WARM_REBOOT - 1;
- @VisibleForTesting
- static final String TAG = "RescueParty";
- @VisibleForTesting
- static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);
- @VisibleForTesting
- static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS;
- // The DeviceConfig namespace containing all RescueParty switches.
- @VisibleForTesting
- static final String NAMESPACE_CONFIGURATION = "configuration";
- @VisibleForTesting
- static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG =
- "namespace_to_package_mapping";
- @VisibleForTesting
- static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440;
-
- private static final String NAME = "rescue-party-observer";
-
- private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
- private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
- private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
- "persist.device_config.configuration.disable_rescue_party";
- private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
- "persist.device_config.configuration.disable_rescue_party_factory_reset";
- private static final String PROP_THROTTLE_DURATION_MIN_FLAG =
- "persist.device_config.configuration.rescue_party_throttle_duration_min";
-
- private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
-
- /**
- * EventLog tags used when logging into the event log. Note the values must be sync with
- * frameworks/base/services/core/java/com/android/server/EventLogTags.logtags to get correct
- * name translation.
- */
- private static final int LOG_TAG_RESCUE_SUCCESS = 2902;
- private static final int LOG_TAG_RESCUE_FAILURE = 2903;
-
- /** Register the Rescue Party observer as a Package Watchdog health observer */
- public static void registerHealthObserver(Context context) {
- PackageWatchdog.getInstance(context).registerHealthObserver(
- context.getMainExecutor(), RescuePartyObserver.getInstance(context));
- }
-
- private static boolean isDisabled() {
- // Check if we're explicitly enabled for testing
- if (SystemProperties.getBoolean(PROP_ENABLE_RESCUE, false)) {
- return false;
- }
-
- // We're disabled if the DeviceConfig disable flag is set to true.
- // This is in case that an emergency rollback of the feature is needed.
- if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
- Slog.v(TAG, "Disabled because of DeviceConfig flag");
- return true;
- }
-
- // We're disabled on all engineering devices
- if (Build.TYPE.equals("eng")) {
- Slog.v(TAG, "Disabled because of eng build");
- return true;
- }
-
- // We're disabled on userdebug devices connected over USB, since that's
- // a decent signal that someone is actively trying to debug the device,
- // or that it's in a lab environment.
- if (Build.TYPE.equals("userdebug") && isUsbActive()) {
- Slog.v(TAG, "Disabled because of active USB connection");
- return true;
- }
-
- // One last-ditch check
- if (SystemProperties.getBoolean(PROP_DISABLE_RESCUE, false)) {
- Slog.v(TAG, "Disabled because of manual property");
- return true;
- }
-
- return false;
- }
-
- /**
- * Check if we're currently attempting to reboot for a factory reset. This method must
- * return true if RescueParty tries to reboot early during a boot loop, since the device
- * will not be fully booted at this time.
- */
- public static boolean isRecoveryTriggeredReboot() {
- return isFactoryResetPropertySet() || isRebootPropertySet();
- }
-
- static boolean isFactoryResetPropertySet() {
- return CrashRecoveryProperties.attemptingFactoryReset().orElse(false);
- }
-
- static boolean isRebootPropertySet() {
- return CrashRecoveryProperties.attemptingReboot().orElse(false);
- }
-
- protected static long getLastFactoryResetTimeMs() {
- return CrashRecoveryProperties.lastFactoryResetTimeMs().orElse(0L);
- }
-
- protected static int getMaxRescueLevelAttempted() {
- return CrashRecoveryProperties.maxRescueLevelAttempted().orElse(LEVEL_NONE);
- }
-
- protected static void setFactoryResetProperty(boolean value) {
- CrashRecoveryProperties.attemptingFactoryReset(value);
- }
- protected static void setRebootProperty(boolean value) {
- CrashRecoveryProperties.attemptingReboot(value);
- }
-
- protected static void setLastFactoryResetTimeMs(long value) {
- CrashRecoveryProperties.lastFactoryResetTimeMs(value);
- }
-
- protected static void setMaxRescueLevelAttempted(int level) {
- CrashRecoveryProperties.maxRescueLevelAttempted(level);
- }
-
- @VisibleForTesting
- static long getElapsedRealtime() {
- return SystemClock.elapsedRealtime();
- }
-
- private static int getMaxRescueLevel(boolean mayPerformReboot) {
- if (Flags.recoverabilityDetection()) {
- if (!mayPerformReboot
- || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return SystemProperties.getInt(RESCUE_NON_REBOOT_LEVEL_LIMIT,
- DEFAULT_RESCUE_NON_REBOOT_LEVEL_LIMIT);
- }
- return RESCUE_LEVEL_FACTORY_RESET;
- } else {
- if (!mayPerformReboot
- || SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
- }
- return LEVEL_FACTORY_RESET;
- }
- }
-
- private static int getMaxRescueLevel() {
- if (!SystemProperties.getBoolean(PROP_DISABLE_FACTORY_RESET_FLAG, false)) {
- return Level.factoryReset();
- }
- return Level.reboot();
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- *
- * @param mitigationCount: the mitigation attempt number (1 = first attempt etc.)
- * @param mayPerformReboot: whether or not a reboot and factory reset may be performed
- * for the given failure.
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static int getRescueLevel(int mitigationCount, boolean mayPerformReboot) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- if (mitigationCount == 1) {
- return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
- } else if (mitigationCount == 2) {
- return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
- } else if (mitigationCount == 3) {
- return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
- } else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_WARM_REBOOT);
- } else if (mitigationCount >= 5) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), LEVEL_FACTORY_RESET);
- } else {
- Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
- return LEVEL_NONE;
- }
- } else {
- if (mitigationCount == 1) {
- return Level.reboot();
- } else if (mitigationCount >= 2) {
- return Math.min(getMaxRescueLevel(), Level.factoryReset());
- } else {
- Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
- return LEVEL_NONE;
- }
- }
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- * When failedPackage is null then 1st and 2nd mitigation counts are redundant (scoped and
- * all device config reset). Behaves as if one mitigation attempt was already done.
- *
- * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
- * @param mayPerformReboot whether or not a reboot and factory reset may be performed
- * for the given failure.
- * @param failedPackage in case of bootloop this is null.
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static @RescueLevels int getRescueLevel(int mitigationCount, boolean mayPerformReboot,
- @Nullable VersionedPackage failedPackage) {
- // Skipping RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET since it's not defined without a failed
- // package.
- if (failedPackage == null && mitigationCount > 0) {
- mitigationCount += 1;
- }
- if (mitigationCount == 1) {
- return RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET;
- } else if (mitigationCount == 2) {
- return RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET;
- } else if (mitigationCount == 3) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_WARM_REBOOT);
- } else if (mitigationCount == 4) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
- } else if (mitigationCount == 5) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES);
- } else if (mitigationCount == 6) {
- return Math.min(getMaxRescueLevel(mayPerformReboot),
- RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS);
- } else if (mitigationCount >= 7) {
- return Math.min(getMaxRescueLevel(mayPerformReboot), RESCUE_LEVEL_FACTORY_RESET);
- } else {
- return RESCUE_LEVEL_NONE;
- }
- }
-
- /**
- * Get the rescue level to perform if this is the n-th attempt at mitigating failure.
- *
- * @param mitigationCount the mitigation attempt number (1 = first attempt etc.).
- * @return the rescue level for the n-th mitigation attempt.
- */
- private static @RescueLevels int getRescueLevel(int mitigationCount) {
- if (mitigationCount == 1) {
- return Level.reboot();
- } else if (mitigationCount >= 2) {
- return Math.min(getMaxRescueLevel(), Level.factoryReset());
- } else {
- return Level.none();
- }
- }
-
- private static void executeRescueLevel(Context context, @Nullable String failedPackage,
- int level) {
- Slog.w(TAG, "Attempting rescue level " + levelToString(level));
- try {
- executeRescueLevelInternal(context, level, failedPackage);
- EventLog.writeEvent(LOG_TAG_RESCUE_SUCCESS, level);
- String successMsg = "Finished rescue level " + levelToString(level);
- if (!TextUtils.isEmpty(failedPackage)) {
- successMsg += " for package " + failedPackage;
- }
- logCrashRecoveryEvent(Log.DEBUG, successMsg);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- }
-
- private static void executeRescueLevelInternal(Context context, int level, @Nullable
- String failedPackage) throws Exception {
- if (Flags.recoverabilityDetection()) {
- executeRescueLevelInternalNew(context, level, failedPackage);
- } else {
- executeRescueLevelInternalOld(context, level, failedPackage);
- }
- }
-
- private static void executeRescueLevelInternalOld(Context context, int level, @Nullable
- String failedPackage) throws Exception {
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
- level, levelToString(level));
- // Try our best to reset all settings possible, and once finished
- // rethrow any exception that we encountered
- Exception res = null;
- switch (level) {
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- break;
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- break;
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- break;
- case LEVEL_WARM_REBOOT:
- executeWarmReboot(context, level, failedPackage);
- break;
- case LEVEL_FACTORY_RESET:
- // Before the completion of Reboot, if any crash happens then PackageWatchdog
- // escalates to next level i.e. factory reset, as they happen in separate threads.
- // Adding a check to prevent factory reset to execute before above reboot completes.
- // Note: this reboot property is not persistent resets after reboot is completed.
- if (isRebootPropertySet()) {
- return;
- }
- executeFactoryReset(context, level, failedPackage);
- break;
- }
-
- if (res != null) {
- throw res;
- }
- }
-
- private static void executeRescueLevelInternalNew(Context context, @RescueLevels int level,
- @Nullable String failedPackage) throws Exception {
- CrashRecoveryStatsLog.write(CrashRecoveryStatsLog.RESCUE_PARTY_RESET_REPORTED,
- level, levelToString(level));
- switch (level) {
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- break;
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- break;
- case RESCUE_LEVEL_WARM_REBOOT:
- executeWarmReboot(context, level, failedPackage);
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- // do nothing
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- // do nothing
- break;
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- // do nothing
- break;
- case RESCUE_LEVEL_FACTORY_RESET:
- // Before the completion of Reboot, if any crash happens then PackageWatchdog
- // escalates to next level i.e. factory reset, as they happen in separate threads.
- // Adding a check to prevent factory reset to execute before above reboot completes.
- // Note: this reboot property is not persistent resets after reboot is completed.
- if (isRebootPropertySet()) {
- return;
- }
- executeFactoryReset(context, level, failedPackage);
- break;
- }
- }
-
- private static void executeWarmReboot(Context context, int level,
- @Nullable String failedPackage) {
- if (Flags.deprecateFlagsAndSettingsResets()) {
- if (shouldThrottleReboot()) {
- return;
- }
- }
-
- // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
- // when device shutting down.
- setRebootProperty(true);
-
- if (Flags.synchronousRebootInRescueParty()) {
- try {
- PowerManager pm = context.getSystemService(PowerManager.class);
- if (pm != null) {
- pm.reboot(TAG);
- }
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- } else {
- Runnable runnable = () -> {
- try {
- PowerManager pm = context.getSystemService(PowerManager.class);
- if (pm != null) {
- pm.reboot(TAG);
- }
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- }
- }
-
- private static void executeFactoryReset(Context context, int level,
- @Nullable String failedPackage) {
- if (Flags.deprecateFlagsAndSettingsResets()) {
- if (shouldThrottleReboot()) {
- return;
- }
- }
- setFactoryResetProperty(true);
- long now = System.currentTimeMillis();
- setLastFactoryResetTimeMs(now);
-
- if (Flags.synchronousRebootInRescueParty()) {
- try {
- RecoverySystem.rebootPromptAndWipeUserData(context, TAG + "," + failedPackage);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- } else {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- try {
- RecoverySystem.rebootPromptAndWipeUserData(context,
- TAG + "," + failedPackage);
- } catch (Throwable t) {
- logRescueException(level, failedPackage, t);
- }
- }
- };
- Thread thread = new Thread(runnable);
- thread.start();
- }
- }
-
-
- private static String getCompleteMessage(Throwable t) {
- final StringBuilder builder = new StringBuilder();
- builder.append(t.getMessage());
- while ((t = t.getCause()) != null) {
- builder.append(": ").append(t.getMessage());
- }
- return builder.toString();
- }
-
- private static void logRescueException(int level, @Nullable String failedPackageName,
- Throwable t) {
- final String msg = getCompleteMessage(t);
- EventLog.writeEvent(LOG_TAG_RESCUE_FAILURE, level, msg);
- String failureMsg = "Failed rescue level " + levelToString(level);
- if (!TextUtils.isEmpty(failedPackageName)) {
- failureMsg += " for package " + failedPackageName;
- }
- logCrashRecoveryEvent(Log.ERROR, failureMsg + ": " + msg);
- }
-
- private static int mapRescueLevelToUserImpact(int rescueLevel) {
- if (Flags.recoverabilityDetection()) {
- switch (rescueLevel) {
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_40;
- case RESCUE_LEVEL_WARM_REBOOT:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_71;
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_75;
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_80;
- case RESCUE_LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- default:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- } else {
- switch (rescueLevel) {
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10;
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- case LEVEL_WARM_REBOOT:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50;
- case LEVEL_FACTORY_RESET:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100;
- default:
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- }
- }
-
- /**
- * Handle mitigation action for package failures. This observer will be register to Package
- * Watchdog and will receive calls about package failures. This observer is persistent so it
- * may choose to mitigate failures for packages it has not explicitly asked to observe.
- */
- public static class RescuePartyObserver implements PackageHealthObserver {
-
- private final Context mContext;
- private final Map<String, Set<String>> mCallingPackageNamespaceSetMap = new HashMap<>();
- private final Map<String, Set<String>> mNamespaceCallingPackageSetMap = new HashMap<>();
-
- @GuardedBy("RescuePartyObserver.class")
- static RescuePartyObserver sRescuePartyObserver;
-
- private RescuePartyObserver(Context context) {
- mContext = context;
- }
-
- /** Creates or gets singleton instance of RescueParty. */
- public static RescuePartyObserver getInstance(Context context) {
- synchronized (RescuePartyObserver.class) {
- if (sRescuePartyObserver == null) {
- sRescuePartyObserver = new RescuePartyObserver(context);
- }
- return sRescuePartyObserver;
- }
- }
-
- /** Gets singleton instance. It returns null if the instance is not created yet.*/
- @Nullable
- public static RescuePartyObserver getInstanceIfCreated() {
- synchronized (RescuePartyObserver.class) {
- return sRescuePartyObserver;
- }
- }
-
- @VisibleForTesting
- static void reset() {
- synchronized (RescuePartyObserver.class) {
- sRescuePartyObserver = null;
- }
- }
-
- @Override
- public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
- || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- mayPerformReboot(failedPackage), failedPackage));
- } else {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
- }
- } else {
- impact = mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- mayPerformReboot(failedPackage)));
- }
- }
-
- Slog.i(TAG, "Checking available remediations for health check failure."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " available impact: " + impact);
- return impact;
- }
-
- @Override
- public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- if (isDisabled()) {
- return MITIGATION_RESULT_SKIPPED;
- }
- Slog.i(TAG, "Executing remediation."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " mitigationCount: " + mitigationCount);
- if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
- || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
- final int level;
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage),
- failedPackage);
- } else {
- level = getRescueLevel(mitigationCount);
- }
- } else {
- level = getRescueLevel(mitigationCount, mayPerformReboot(failedPackage));
- }
- executeRescueLevel(mContext,
- failedPackage == null ? null : failedPackage.getPackageName(), level);
- return MITIGATION_RESULT_SUCCESS;
- } else {
- return MITIGATION_RESULT_SKIPPED;
- }
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean mayObservePackage(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- // A package is a module if this is non-null
- if (pm.getModuleInfo(packageName, 0) != null) {
- return true;
- }
- } catch (PackageManager.NameNotFoundException | IllegalStateException ignore) {
- }
-
- return isPersistentSystemApp(packageName);
- }
-
- @Override
- public int onBootLoop(int mitigationCount) {
- if (isDisabled()) {
- return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
- true, /*failedPackage=*/ null));
- } else {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount));
- }
- } else {
- return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true));
- }
- }
-
- @Override
- public int onExecuteBootLoopMitigation(int mitigationCount) {
- if (isDisabled()) {
- return MITIGATION_RESULT_SKIPPED;
- }
- boolean mayPerformReboot = !shouldThrottleReboot();
- final int level;
- if (Flags.recoverabilityDetection()) {
- if (!Flags.deprecateFlagsAndSettingsResets()) {
- level = getRescueLevel(mitigationCount, mayPerformReboot,
- /*failedPackage=*/ null);
- } else {
- level = getRescueLevel(mitigationCount);
- }
- } else {
- level = getRescueLevel(mitigationCount, mayPerformReboot);
- }
- executeRescueLevel(mContext, /*failedPackage=*/ null, level);
- return MITIGATION_RESULT_SUCCESS;
- }
-
- @Override
- public String getUniqueIdentifier() {
- return NAME;
- }
-
- /**
- * Returns {@code true} if the failing package is non-null and performing a reboot or
- * prompting a factory reset is an acceptable mitigation strategy for the package's
- * failure, {@code false} otherwise.
- */
- private boolean mayPerformReboot(@Nullable VersionedPackage failingPackage) {
- if (failingPackage == null) {
- return false;
- }
- if (shouldThrottleReboot()) {
- return false;
- }
-
- return isPersistentSystemApp(failingPackage.getPackageName());
- }
-
- private boolean isPersistentSystemApp(@NonNull String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private synchronized Set<String> getCallingPackagesSet(String namespace) {
- return mNamespaceCallingPackageSetMap.get(namespace);
- }
- }
-
- /**
- * Returns {@code true} if Rescue Party is allowed to attempt a reboot or factory reset.
- * Will return {@code false} if a factory reset was already offered recently.
- */
- private static boolean shouldThrottleReboot() {
- Long lastResetTime = getLastFactoryResetTimeMs();
- long now = System.currentTimeMillis();
- long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG,
- DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN);
- return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin);
- }
-
- /**
- * Hacky test to check if the device has an active USB connection, which is
- * a good proxy for someone doing local development work.
- */
- private static boolean isUsbActive() {
- if (SystemProperties.getBoolean(PROP_VIRTUAL_DEVICE, false)) {
- Slog.v(TAG, "Assuming virtual device is connected over USB");
- return true;
- }
- try {
- final String state = FileUtils
- .readTextFile(new File("/sys/class/android_usb/android0/state"), 128, "");
- return "CONFIGURED".equals(state.trim());
- } catch (Throwable t) {
- Slog.w(TAG, "Failed to determine if device was on USB", t);
- return false;
- }
- }
-
- private static class Level {
- static int none() {
- return Flags.recoverabilityDetection() ? RESCUE_LEVEL_NONE : LEVEL_NONE;
- }
-
- static int reboot() {
- return Flags.recoverabilityDetection() ? RESCUE_LEVEL_WARM_REBOOT : LEVEL_WARM_REBOOT;
- }
-
- static int factoryReset() {
- return Flags.recoverabilityDetection()
- ? RESCUE_LEVEL_FACTORY_RESET
- : LEVEL_FACTORY_RESET;
- }
- }
-
- private static String levelToString(int level) {
- if (Flags.recoverabilityDetection()) {
- switch (level) {
- case RESCUE_LEVEL_NONE:
- return "NONE";
- case RESCUE_LEVEL_SCOPED_DEVICE_CONFIG_RESET:
- return "SCOPED_DEVICE_CONFIG_RESET";
- case RESCUE_LEVEL_ALL_DEVICE_CONFIG_RESET:
- return "ALL_DEVICE_CONFIG_RESET";
- case RESCUE_LEVEL_WARM_REBOOT:
- return "WARM_REBOOT";
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
- case RESCUE_LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return "RESET_SETTINGS_UNTRUSTED_CHANGES";
- case RESCUE_LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return "RESET_SETTINGS_TRUSTED_DEFAULTS";
- case RESCUE_LEVEL_FACTORY_RESET:
- return "FACTORY_RESET";
- default:
- return Integer.toString(level);
- }
- } else {
- switch (level) {
- case LEVEL_NONE:
- return "NONE";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
- return "RESET_SETTINGS_UNTRUSTED_DEFAULTS";
- case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
- return "RESET_SETTINGS_UNTRUSTED_CHANGES";
- case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
- return "RESET_SETTINGS_TRUSTED_DEFAULTS";
- case LEVEL_WARM_REBOOT:
- return "WARM_REBOOT";
- case LEVEL_FACTORY_RESET:
- return "FACTORY_RESET";
- default:
- return Integer.toString(level);
- }
- }
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
deleted file mode 100644
index 8a81aaa1e636..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryModule.java
+++ /dev/null
@@ -1,58 +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.server.crashrecovery;
-
-import android.content.Context;
-
-import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
-import com.android.server.SystemService;
-
-
-/** This class encapsulate the lifecycle methods of CrashRecovery module.
- *
- * @hide
- */
-public class CrashRecoveryModule {
- private static final String TAG = "CrashRecoveryModule";
-
- /** Lifecycle definition for CrashRecovery module. */
- public static class Lifecycle extends SystemService {
- private Context mSystemContext;
- private PackageWatchdog mPackageWatchdog;
-
- public Lifecycle(Context context) {
- super(context);
- mSystemContext = context;
- mPackageWatchdog = PackageWatchdog.getInstance(context);
- }
-
- @Override
- public void onStart() {
- RescueParty.registerHealthObserver(mSystemContext);
- mPackageWatchdog.registerShutdownBroadcastReceiver();
- mPackageWatchdog.noteBoot();
- }
-
- @Override
- public void onBootPhase(int phase) {
- if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
- mPackageWatchdog.onPackagesReady();
- }
- }
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java b/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
deleted file mode 100644
index 2e2a93776f9d..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/crashrecovery/CrashRecoveryUtils.java
+++ /dev/null
@@ -1,85 +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.server.crashrecovery;
-
-import android.os.Environment;
-import android.util.IndentingPrintWriter;
-import android.util.Log;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-
-/**
- * Class containing helper methods for the CrashRecoveryModule.
- *
- * @hide
- */
-public class CrashRecoveryUtils {
- private static final String TAG = "CrashRecoveryUtils";
- private static final long MAX_CRITICAL_INFO_DUMP_SIZE = 1000 * 1000; // ~1MB
- private static final Object sFileLock = new Object();
-
- /** Persist recovery related events in crashrecovery events file.**/
- public static void logCrashRecoveryEvent(int priority, String msg) {
- Log.println(priority, TAG, msg);
- try {
- File fname = getCrashRecoveryEventsFile();
- synchronized (sFileLock) {
- FileOutputStream out = new FileOutputStream(fname, true);
- PrintWriter pw = new PrintWriter(out);
- String dateString = LocalDateTime.now(ZoneId.systemDefault()).toString();
- pw.println(dateString + ": " + msg);
- pw.close();
- }
- } catch (IOException e) {
- Log.e(TAG, "Unable to log CrashRecoveryEvents " + e.getMessage());
- }
- }
-
- /** Dump recovery related events from crashrecovery events file.**/
- public static void dumpCrashRecoveryEvents(IndentingPrintWriter pw) {
- pw.println("CrashRecovery Events: ");
- pw.increaseIndent();
- final File file = getCrashRecoveryEventsFile();
- final long skipSize = file.length() - MAX_CRITICAL_INFO_DUMP_SIZE;
- synchronized (sFileLock) {
- try (BufferedReader in = new BufferedReader(new FileReader(file))) {
- if (skipSize > 0) {
- in.skip(skipSize);
- }
- String line;
- while ((line = in.readLine()) != null) {
- pw.println(line);
- }
- } catch (IOException e) {
- Log.e(TAG, "Unable to dump CrashRecoveryEvents " + e.getMessage());
- }
- }
- pw.decreaseIndent();
- }
-
- private static File getCrashRecoveryEventsFile() {
- File systemDir = new File(Environment.getDataDirectory(), "system");
- return new File(systemDir, "crashrecovery-events.txt");
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
deleted file mode 100644
index 4978df491c62..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ /dev/null
@@ -1,785 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.rollback;
-
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SKIPPED;
-import static com.android.server.PackageWatchdog.MITIGATION_RESULT_SUCCESS;
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-
-import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SuppressLint;
-import android.annotation.SystemApi;
-import android.annotation.WorkerThread;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
-import android.crashrecovery.flags.Flags;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.PowerManager;
-import android.os.SystemProperties;
-import android.sysprop.CrashRecoveryProperties;
-import android.util.ArraySet;
-import android.util.FileUtils;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
-import com.android.server.PackageWatchdog;
-import com.android.server.PackageWatchdog.FailureReasons;
-import com.android.server.PackageWatchdog.PackageHealthObserver;
-import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * {@link PackageHealthObserver} for {@link RollbackManagerService}.
- * This class monitors crashes and triggers RollbackManager rollback accordingly.
- * It also monitors native crashes for some short while after boot.
- *
- * @hide
- */
-@FlaggedApi(Flags.FLAG_ENABLE_CRASHRECOVERY)
-@SuppressLint({"CallbackName"})
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-public final class RollbackPackageHealthObserver implements PackageHealthObserver {
- private static final String TAG = "RollbackPackageHealthObserver";
- private static final String NAME = "rollback-observer";
- private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
-
- private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
- | ApplicationInfo.FLAG_SYSTEM;
-
- private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
- "persist.device_config.configuration.disable_high_impact_rollback";
-
- private final Context mContext;
- private final Handler mHandler;
- private final File mLastStagedRollbackIdsFile;
- private final File mTwoPhaseRollbackEnabledFile;
- // Staged rollback ids that have been committed but their session is not yet ready
- private final Set<Integer> mPendingStagedRollbackIds = new ArraySet<>();
- // True if needing to roll back only rebootless apexes when native crash happens
- private boolean mTwoPhaseRollbackEnabled;
-
- @VisibleForTesting
- public RollbackPackageHealthObserver(@NonNull Context context) {
- mContext = context;
- HandlerThread handlerThread = new HandlerThread("RollbackPackageHealthObserver");
- handlerThread.start();
- mHandler = new Handler(handlerThread.getLooper());
- File dataDir = new File(Environment.getDataDirectory(), "rollback-observer");
- dataDir.mkdirs();
- mLastStagedRollbackIdsFile = new File(dataDir, "last-staged-rollback-ids");
- mTwoPhaseRollbackEnabledFile = new File(dataDir, "two-phase-rollback-enabled");
- PackageWatchdog.getInstance(mContext).registerHealthObserver(context.getMainExecutor(),
- this);
-
- if (SystemProperties.getBoolean("sys.boot_completed", false)) {
- // Load the value from the file if system server has crashed and restarted
- mTwoPhaseRollbackEnabled = readBoolean(mTwoPhaseRollbackEnabledFile);
- } else {
- // Disable two-phase rollback for a normal reboot. We assume the rebootless apex
- // installed before reboot is stable if native crash didn't happen.
- mTwoPhaseRollbackEnabled = false;
- writeBoolean(mTwoPhaseRollbackEnabledFile, false);
- }
- }
-
- @Override
- public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
- @FailureReasons int failureReason, int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
- if (!lowImpactRollbacks.isEmpty()) {
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- // For native crashes, we will directly roll back any available rollbacks at low
- // impact level
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getRollbackForPackage(failedPackage, lowImpactRollbacks) != null) {
- // Rollback is available for crashing low impact package
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else {
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- }
- }
- } else {
- boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class)
- .getAvailableRollbacks().isEmpty();
-
- if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH
- && anyRollbackAvailable) {
- // For native crashes, we will directly roll back any available rollbacks
- // Note: For non-native crashes the rollback-all step has higher impact
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (getAvailableRollback(failedPackage) != null) {
- // Rollback is available, we may get a callback into #onExecuteHealthCheckMitigation
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30;
- } else if (anyRollbackAvailable) {
- // If any rollbacks are available, we will commit them
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- }
- }
-
- Slog.i(TAG, "Checking available remediations for health check failure."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " failureReason: " + failureReason
- + " available impact: " + impact);
- return impact;
- }
-
- @Override
- public int onExecuteHealthCheckMitigation(@Nullable VersionedPackage failedPackage,
- @FailureReasons int rollbackReason, int mitigationCount) {
- Slog.i(TAG, "Executing remediation."
- + " failedPackage: "
- + (failedPackage == null ? null : failedPackage.getPackageName())
- + " rollbackReason: " + rollbackReason
- + " mitigationCount: " + mitigationCount);
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- return MITIGATION_RESULT_SUCCESS;
- }
-
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_LOW);
- RollbackInfo rollback = getRollbackForPackage(failedPackage, lowImpactRollbacks);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
- } else if (!lowImpactRollbacks.isEmpty()) {
- // Apply all available low impact rollbacks.
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- }
- } else {
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- mHandler.post(() -> rollbackAll(rollbackReason));
- return MITIGATION_RESULT_SUCCESS;
- }
-
- RollbackInfo rollback = getAvailableRollback(failedPackage);
- if (rollback != null) {
- mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason));
- } else {
- mHandler.post(() -> rollbackAll(rollbackReason));
- }
- }
-
- // Assume rollbacks executed successfully
- return MITIGATION_RESULT_SUCCESS;
- }
-
- @Override
- public int onBootLoop(int mitigationCount) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
- if (!availableRollbacks.isEmpty()) {
- impact = getUserImpactBasedOnRollbackImpactLevel(availableRollbacks);
- }
- }
- return impact;
- }
-
- @Override
- public int onExecuteBootLoopMitigation(int mitigationCount) {
- if (Flags.recoverabilityDetection()) {
- List<RollbackInfo> availableRollbacks = getAvailableRollbacks();
-
- triggerLeastImpactLevelRollback(availableRollbacks,
- PackageWatchdog.FAILURE_REASON_BOOT_LOOP);
- return MITIGATION_RESULT_SUCCESS;
- }
- return MITIGATION_RESULT_SKIPPED;
- }
-
- @Override
- @NonNull
- public String getUniqueIdentifier() {
- return NAME;
- }
-
- @Override
- public boolean isPersistent() {
- return true;
- }
-
- @Override
- public boolean mayObservePackage(@NonNull String packageName) {
- if (getAvailableRollbacks().isEmpty()) {
- return false;
- }
- return isPersistentSystemApp(packageName);
- }
-
- private List<RollbackInfo> getAvailableRollbacks() {
- return mContext.getSystemService(RollbackManager.class).getAvailableRollbacks();
- }
-
- private boolean isPersistentSystemApp(@NonNull String packageName) {
- PackageManager pm = mContext.getPackageManager();
- try {
- ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
- return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- private void assertInWorkerThread() {
- Preconditions.checkState(mHandler.getLooper().isCurrentThread());
- }
-
- @AnyThread
- @NonNull
- public void notifyRollbackAvailable(@NonNull RollbackInfo rollback) {
- mHandler.post(() -> {
- // Enable two-phase rollback when a rebootless apex rollback is made available.
- // We assume the rebootless apex is stable and is less likely to be the cause
- // if native crash doesn't happen before reboot. So we will clear the flag and disable
- // two-phase rollback after reboot.
- if (isRebootlessApex(rollback)) {
- mTwoPhaseRollbackEnabled = true;
- writeBoolean(mTwoPhaseRollbackEnabledFile, true);
- }
- });
- }
-
- private static boolean isRebootlessApex(RollbackInfo rollback) {
- if (!rollback.isStaged()) {
- for (PackageRollbackInfo info : rollback.getPackages()) {
- if (info.isApex()) {
- return true;
- }
- }
- }
- return false;
- }
-
- /** Verifies the rollback state after a reboot and schedules polling for sometime after reboot
- * to check for native crashes and mitigate them if needed.
- */
- @AnyThread
- public void onBootCompletedAsync() {
- mHandler.post(()->onBootCompleted());
- }
-
- @WorkerThread
- private void onBootCompleted() {
- assertInWorkerThread();
-
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- if (!rollbackManager.getAvailableRollbacks().isEmpty()) {
- // TODO(gavincorkery): Call into Package Watchdog from outside the observer
- PackageWatchdog.getInstance(mContext).scheduleCheckAndMitigateNativeCrashes();
- }
-
- SparseArray<String> rollbackIds = popLastStagedRollbackIds();
- for (int i = 0; i < rollbackIds.size(); i++) {
- WatchdogRollbackLogger.logRollbackStatusOnBoot(mContext,
- rollbackIds.keyAt(i), rollbackIds.valueAt(i),
- rollbackManager.getRecentlyCommittedRollbacks());
- }
- }
-
- @AnyThread
- private RollbackInfo getAvailableRollback(VersionedPackage failedPackage) {
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- for (RollbackInfo rollback : rollbackManager.getAvailableRollbacks()) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
- return rollback;
- }
- // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
- // to rely on complicated reasoning as below
-
- // Due to b/147666157, for apk in apex, we do not know the version we are rolling
- // back from. But if a package X is embedded in apex A exclusively (not embedded in
- // any other apex), which is not guaranteed, then it is sufficient to check only
- // package names here, as the version of failedPackage and the PackageRollbackInfo
- // can't be different. If failedPackage has a higher version, then it must have
- // been updated somehow. There are two ways: it was updated by an update of apex A
- // or updated directly as apk. In both cases, this rollback would have gotten
- // expired when onPackageReplaced() was called. Since the rollback exists, it has
- // same version as failedPackage.
- if (packageRollback.isApkInApex()
- && packageRollback.getVersionRolledBackFrom().getPackageName()
- .equals(failedPackage.getPackageName())) {
- return rollback;
- }
- }
- }
- return null;
- }
-
- @AnyThread
- private RollbackInfo getRollbackForPackage(@Nullable VersionedPackage failedPackage,
- List<RollbackInfo> availableRollbacks) {
- if (failedPackage == null) {
- return null;
- }
-
- for (RollbackInfo rollback : availableRollbacks) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
- return rollback;
- }
- // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
- // to rely on complicated reasoning as below
-
- // Due to b/147666157, for apk in apex, we do not know the version we are rolling
- // back from. But if a package X is embedded in apex A exclusively (not embedded in
- // any other apex), which is not guaranteed, then it is sufficient to check only
- // package names here, as the version of failedPackage and the PackageRollbackInfo
- // can't be different. If failedPackage has a higher version, then it must have
- // been updated somehow. There are two ways: it was updated by an update of apex A
- // or updated directly as apk. In both cases, this rollback would have gotten
- // expired when onPackageReplaced() was called. Since the rollback exists, it has
- // same version as failedPackage.
- if (packageRollback.isApkInApex()
- && packageRollback.getVersionRolledBackFrom().getPackageName()
- .equals(failedPackage.getPackageName())) {
- return rollback;
- }
- }
- }
- return null;
- }
-
- /**
- * Returns {@code true} if staged session associated with {@code rollbackId} was marked
- * as handled, {@code false} if already handled.
- */
- @WorkerThread
- private boolean markStagedSessionHandled(int rollbackId) {
- assertInWorkerThread();
- return mPendingStagedRollbackIds.remove(rollbackId);
- }
-
- /**
- * Returns {@code true} if all pending staged rollback sessions were marked as handled,
- * {@code false} if there is any left.
- */
- @WorkerThread
- private boolean isPendingStagedSessionsEmpty() {
- assertInWorkerThread();
- return mPendingStagedRollbackIds.isEmpty();
- }
-
- private static boolean readBoolean(File file) {
- try (FileInputStream fis = new FileInputStream(file)) {
- return fis.read() == 1;
- } catch (IOException ignore) {
- return false;
- }
- }
-
- private static void writeBoolean(File file, boolean value) {
- try (FileOutputStream fos = new FileOutputStream(file)) {
- fos.write(value ? 1 : 0);
- fos.flush();
- FileUtils.sync(fos);
- } catch (IOException ignore) {
- }
- }
-
- @WorkerThread
- private void saveStagedRollbackId(int stagedRollbackId, @Nullable VersionedPackage logPackage) {
- assertInWorkerThread();
- writeStagedRollbackId(mLastStagedRollbackIdsFile, stagedRollbackId, logPackage);
- }
-
- static void writeStagedRollbackId(File file, int stagedRollbackId,
- @Nullable VersionedPackage logPackage) {
- try {
- FileOutputStream fos = new FileOutputStream(file, true);
- PrintWriter pw = new PrintWriter(fos);
- String logPackageName = logPackage != null ? logPackage.getPackageName() : "";
- pw.append(String.valueOf(stagedRollbackId)).append(",").append(logPackageName);
- pw.println();
- pw.flush();
- FileUtils.sync(fos);
- pw.close();
- } catch (IOException e) {
- Slog.e(TAG, "Failed to save last staged rollback id", e);
- file.delete();
- }
- }
-
- @WorkerThread
- private SparseArray<String> popLastStagedRollbackIds() {
- assertInWorkerThread();
- try {
- return readStagedRollbackIds(mLastStagedRollbackIdsFile);
- } finally {
- mLastStagedRollbackIdsFile.delete();
- }
- }
-
- static SparseArray<String> readStagedRollbackIds(File file) {
- SparseArray<String> result = new SparseArray<>();
- try {
- String line;
- BufferedReader reader = new BufferedReader(new FileReader(file));
- while ((line = reader.readLine()) != null) {
- // Each line is of the format: "id,logging_package"
- String[] values = line.trim().split(",");
- String rollbackId = values[0];
- String logPackageName = "";
- if (values.length > 1) {
- logPackageName = values[1];
- }
- result.put(Integer.parseInt(rollbackId), logPackageName);
- }
- } catch (Exception ignore) {
- return new SparseArray<>();
- }
- return result;
- }
-
-
- /**
- * Returns true if the package name is the name of a module.
- */
- @AnyThread
- private boolean isModule(String packageName) {
- // Check if the package is listed among the system modules or is an
- // APK inside an updatable APEX.
- try {
- PackageManager pm = mContext.getPackageManager();
- final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
- String apexPackageName = pkg.getApexPackageName();
- if (apexPackageName != null) {
- packageName = apexPackageName;
- }
-
- return pm.getModuleInfo(packageName, 0 /* flags */) != null;
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- }
-
- /**
- * Rolls back the session that owns {@code failedPackage}
- *
- * @param rollback {@code rollbackInfo} of the {@code failedPackage}
- * @param failedPackage the package that needs to be rolled back
- */
- @WorkerThread
- private void rollbackPackage(RollbackInfo rollback, VersionedPackage failedPackage,
- @FailureReasons int rollbackReason) {
- assertInWorkerThread();
- String failedPackageName = (failedPackage == null ? null : failedPackage.getPackageName());
-
- Slog.i(TAG, "Rolling back package. RollbackId: " + rollback.getRollbackId()
- + " failedPackage: " + failedPackageName
- + " rollbackReason: " + rollbackReason);
- logCrashRecoveryEvent(Log.DEBUG, String.format("Rolling back %s. Reason: %s",
- failedPackageName, rollbackReason));
- final RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- int reasonToLog = WatchdogRollbackLogger.mapFailureReasonToMetric(rollbackReason);
- final String failedPackageToLog;
- if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
- failedPackageToLog = SystemProperties.get(
- "sys.init.updatable_crashing_process_name", "");
- } else {
- failedPackageToLog = failedPackage.getPackageName();
- }
- VersionedPackage logPackageTemp = null;
- if (isModule(failedPackage.getPackageName())) {
- logPackageTemp = WatchdogRollbackLogger.getLogPackage(mContext, failedPackage);
- }
-
- final VersionedPackage logPackage = logPackageTemp;
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE,
- reasonToLog, failedPackageToLog);
-
- Consumer<Intent> onResult = result -> {
- assertInWorkerThread();
- int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
- RollbackManager.STATUS_FAILURE);
- if (status == RollbackManager.STATUS_SUCCESS) {
- if (rollback.isStaged()) {
- int rollbackId = rollback.getRollbackId();
- saveStagedRollbackId(rollbackId, logPackage);
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
- reasonToLog, failedPackageToLog);
-
- } else {
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- reasonToLog, failedPackageToLog);
- }
- } else {
- WatchdogRollbackLogger.logEvent(logPackage,
- CrashRecoveryStatsLog
- .WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
- reasonToLog, failedPackageToLog);
- }
- if (rollback.isStaged()) {
- markStagedSessionHandled(rollback.getRollbackId());
- // Wait for all pending staged sessions to get handled before rebooting.
- if (isPendingStagedSessionsEmpty()) {
- CrashRecoveryProperties.attemptingReboot(true);
- mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
- }
- }
- };
-
- // Define a BroadcastReceiver to handle the result
- BroadcastReceiver rollbackReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent result) {
- mHandler.post(() -> onResult.accept(result));
- }
- };
-
- String intentActionName = CLASS_NAME + rollback.getRollbackId();
- // Register the BroadcastReceiver
- mContext.registerReceiver(rollbackReceiver,
- new IntentFilter(intentActionName),
- Context.RECEIVER_NOT_EXPORTED);
-
- Intent intentReceiver = new Intent(intentActionName);
- intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
- intentReceiver.setPackage(mContext.getPackageName());
- intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-
- PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
- rollback.getRollbackId(),
- intentReceiver,
- PendingIntent.FLAG_MUTABLE);
-
- rollbackManager.commitRollback(rollback.getRollbackId(),
- Collections.singletonList(failedPackage),
- rollbackPendingIntent.getIntentSender());
- }
-
- /**
- * Two-phase rollback:
- * 1. roll back rebootless apexes first
- * 2. roll back all remaining rollbacks if native crash doesn't stop after (1) is done
- *
- * This approach gives us a better chance to correctly attribute native crash to rebootless
- * apex update without rolling back Mainline updates which might contains critical security
- * fixes.
- */
- @WorkerThread
- private boolean useTwoPhaseRollback(List<RollbackInfo> rollbacks) {
- assertInWorkerThread();
- if (!mTwoPhaseRollbackEnabled) {
- return false;
- }
-
- Slog.i(TAG, "Rolling back all rebootless APEX rollbacks");
- boolean found = false;
- for (RollbackInfo rollback : rollbacks) {
- if (isRebootlessApex(rollback)) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback,
- PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
- found = true;
- }
- }
- return found;
- }
-
- /**
- * Rollback the package that has minimum rollback impact level.
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollback
- */
- private void triggerLeastImpactLevelRollback(List<RollbackInfo> availableRollbacks,
- @FailureReasons int rollbackReason) {
- int minRollbackImpactLevel = getMinRollbackImpactLevel(availableRollbacks);
-
- if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_LOW) {
- // Apply all available low impact rollbacks.
- mHandler.post(() -> rollbackAllLowImpact(availableRollbacks, rollbackReason));
- } else if (minRollbackImpactLevel == PackageManager.ROLLBACK_USER_IMPACT_HIGH) {
- // Check disable_high_impact_rollback device config before performing rollback
- if (SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
- return;
- }
- // Rollback one package at a time. If that doesn't resolve the issue, rollback
- // next with same impact level.
- mHandler.post(() -> rollbackHighImpact(availableRollbacks, rollbackReason));
- }
- }
-
- /**
- * sort the available high impact rollbacks by first package name to have a deterministic order.
- * Apply the first available rollback.
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollback
- */
- @WorkerThread
- private void rollbackHighImpact(List<RollbackInfo> availableRollbacks,
- @FailureReasons int rollbackReason) {
- assertInWorkerThread();
- List<RollbackInfo> highImpactRollbacks =
- getRollbacksAvailableForImpactLevel(
- availableRollbacks, PackageManager.ROLLBACK_USER_IMPACT_HIGH);
-
- // sort rollbacks based on package name of the first package. This is to have a
- // deterministic order of rollbacks.
- List<RollbackInfo> sortedHighImpactRollbacks = highImpactRollbacks.stream().sorted(
- Comparator.comparing(a -> a.getPackages().get(0).getPackageName())).toList();
- VersionedPackage firstRollback =
- sortedHighImpactRollbacks
- .get(0)
- .getPackages()
- .get(0)
- .getVersionRolledBackFrom();
- Slog.i(TAG, "Rolling back high impact rollback for package: "
- + firstRollback.getPackageName());
- rollbackPackage(sortedHighImpactRollbacks.get(0), firstRollback, rollbackReason);
- }
-
- @WorkerThread
- private void rollbackAll(@FailureReasons int rollbackReason) {
- assertInWorkerThread();
- RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
- List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
- if (useTwoPhaseRollback(rollbacks)) {
- return;
- }
-
- Slog.i(TAG, "Rolling back all available rollbacks");
- // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
- // pending staged rollbacks are handled.
- for (RollbackInfo rollback : rollbacks) {
- if (rollback.isStaged()) {
- mPendingStagedRollbackIds.add(rollback.getRollbackId());
- }
- }
-
- for (RollbackInfo rollback : rollbacks) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback, rollbackReason);
- }
- }
-
- /**
- * Rollback all available low impact rollbacks
- * @param availableRollbacks all available rollbacks
- * @param rollbackReason reason to rollbacks
- */
- @WorkerThread
- private void rollbackAllLowImpact(
- List<RollbackInfo> availableRollbacks, @FailureReasons int rollbackReason) {
- assertInWorkerThread();
-
- List<RollbackInfo> lowImpactRollbacks = getRollbacksAvailableForImpactLevel(
- availableRollbacks,
- PackageManager.ROLLBACK_USER_IMPACT_LOW);
- if (useTwoPhaseRollback(lowImpactRollbacks)) {
- return;
- }
-
- Slog.i(TAG, "Rolling back all available low impact rollbacks");
- logCrashRecoveryEvent(Log.DEBUG, "Rolling back all available. Reason: " + rollbackReason);
- // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
- // pending staged rollbacks are handled.
- for (RollbackInfo rollback : lowImpactRollbacks) {
- if (rollback.isStaged()) {
- mPendingStagedRollbackIds.add(rollback.getRollbackId());
- }
- }
-
- for (RollbackInfo rollback : lowImpactRollbacks) {
- VersionedPackage firstRollback =
- rollback.getPackages().get(0).getVersionRolledBackFrom();
- rollbackPackage(rollback, firstRollback, rollbackReason);
- }
- }
-
- private List<RollbackInfo> getRollbacksAvailableForImpactLevel(
- List<RollbackInfo> availableRollbacks, int impactLevel) {
- return availableRollbacks.stream()
- .filter(rollbackInfo -> rollbackInfo.getRollbackImpactLevel() == impactLevel)
- .toList();
- }
-
- private int getMinRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
- return availableRollbacks.stream()
- .mapToInt(RollbackInfo::getRollbackImpactLevel)
- .min()
- .orElse(-1);
- }
-
- private int getUserImpactBasedOnRollbackImpactLevel(List<RollbackInfo> availableRollbacks) {
- int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- int minImpact = getMinRollbackImpactLevel(availableRollbacks);
- switch (minImpact) {
- case PackageManager.ROLLBACK_USER_IMPACT_LOW:
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70;
- break;
- case PackageManager.ROLLBACK_USER_IMPACT_HIGH:
- if (!SystemProperties.getBoolean(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, false)) {
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_90;
- }
- break;
- default:
- impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0;
- }
- return impact;
- }
-
- @VisibleForTesting
- Handler getHandler() {
- return mHandler;
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java b/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
deleted file mode 100644
index 9cfed02f9355..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/server/rollback/WatchdogRollbackLogger.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.rollback;
-
-import static com.android.server.crashrecovery.CrashRecoveryUtils.logCrashRecoveryEvent;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE;
-import static com.android.server.crashrecovery.proto.CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Slog;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.PackageWatchdog;
-import com.android.server.crashrecovery.proto.CrashRecoveryStatsLog;
-
-import java.util.List;
-
-/**
- * This class handles the logic for logging Watchdog-triggered rollback events.
- * @hide
- */
-public final class WatchdogRollbackLogger {
- private static final String TAG = "WatchdogRollbackLogger";
-
- private static final String LOGGING_PARENT_KEY = "android.content.pm.LOGGING_PARENT";
-
- private WatchdogRollbackLogger() {
- }
-
- @Nullable
- private static String getLoggingParentName(Context context, @NonNull String packageName) {
- PackageManager packageManager = context.getPackageManager();
- try {
- int flags = PackageManager.MATCH_APEX | PackageManager.GET_META_DATA;
- ApplicationInfo ai = packageManager.getPackageInfo(packageName, flags).applicationInfo;
- if (ai.metaData == null) {
- return null;
- }
- return ai.metaData.getString(LOGGING_PARENT_KEY);
- } catch (Exception e) {
- Slog.w(TAG, "Unable to discover logging parent package: " + packageName, e);
- return null;
- }
- }
-
- /**
- * Returns the logging parent of a given package if it exists, {@code null} otherwise.
- *
- * The logging parent is defined by the {@code android.content.pm.LOGGING_PARENT} field in the
- * metadata of a package's AndroidManifest.xml.
- */
- @VisibleForTesting
- @Nullable
- static VersionedPackage getLogPackage(Context context,
- @NonNull VersionedPackage failingPackage) {
- String logPackageName;
- VersionedPackage loggingParent;
- logPackageName = getLoggingParentName(context, failingPackage.getPackageName());
- if (logPackageName == null) {
- return null;
- }
- try {
- loggingParent = new VersionedPackage(logPackageName, context.getPackageManager()
- .getPackageInfo(logPackageName, 0 /* flags */).getLongVersionCode());
- } catch (PackageManager.NameNotFoundException e) {
- return null;
- }
- return loggingParent;
- }
-
- static void logRollbackStatusOnBoot(Context context, int rollbackId, String logPackageName,
- List<RollbackInfo> recentlyCommittedRollbacks) {
- PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
-
- RollbackInfo rollback = null;
- for (RollbackInfo info : recentlyCommittedRollbacks) {
- if (rollbackId == info.getRollbackId()) {
- rollback = info;
- break;
- }
- }
-
- if (rollback == null) {
- Slog.e(TAG, "rollback info not found for last staged rollback: " + rollbackId);
- return;
- }
-
- // Use the version of the logging parent that was installed before
- // we rolled back for logging purposes.
- VersionedPackage oldLoggingPackage = null;
- if (!TextUtils.isEmpty(logPackageName)) {
- for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
- if (logPackageName.equals(packageRollback.getPackageName())) {
- oldLoggingPackage = packageRollback.getVersionRolledBackFrom();
- break;
- }
- }
- }
-
- int sessionId = rollback.getCommittedSessionId();
- PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
- if (sessionInfo == null) {
- Slog.e(TAG, "On boot completed, could not load session id " + sessionId);
- return;
- }
-
- if (sessionInfo.isStagedSessionApplied()) {
- logEvent(oldLoggingPackage,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
- } else if (sessionInfo.isStagedSessionFailed()) {
- logEvent(oldLoggingPackage,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
- WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
- }
- }
-
- /**
- * Log a Watchdog rollback event to statsd.
- *
- * @param logPackage the package to associate the rollback with.
- * @param type the state of the rollback.
- * @param rollbackReason the reason Watchdog triggered a rollback, if known.
- * @param failingPackageName the failing package or process which triggered the rollback.
- */
- public static void logEvent(@Nullable VersionedPackage logPackage, int type,
- int rollbackReason, @NonNull String failingPackageName) {
- String logMsg = "Watchdog event occurred with type: " + rollbackTypeToString(type)
- + " logPackage: " + logPackage
- + " rollbackReason: " + rollbackReasonToString(rollbackReason)
- + " failedPackageName: " + failingPackageName;
- Slog.i(TAG, logMsg);
- if (logPackage != null) {
- CrashRecoveryStatsLog.write(
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
- type,
- logPackage.getPackageName(),
- logPackage.getVersionCode(),
- rollbackReason,
- failingPackageName,
- new byte[]{});
- } else {
- // In the case that the log package is null, still log an empty string as an
- // indication that retrieving the logging parent failed.
- CrashRecoveryStatsLog.write(
- CrashRecoveryStatsLog.WATCHDOG_ROLLBACK_OCCURRED,
- type,
- "",
- 0,
- rollbackReason,
- failingPackageName,
- new byte[]{});
- }
-
- logTestProperties(logMsg);
- }
-
- /**
- * Writes properties which will be used by rollback tests to check if particular rollback
- * events have occurred.
- */
- private static void logTestProperties(String logMsg) {
- // This property should be on only during the tests
- if (!SystemProperties.getBoolean("persist.sys.rollbacktest.enabled", false)) {
- return;
- }
- logCrashRecoveryEvent(Log.DEBUG, logMsg);
- }
-
- @VisibleForTesting
- static int mapFailureReasonToMetric(@PackageWatchdog.FailureReasons int failureReason) {
- switch (failureReason) {
- case PackageWatchdog.FAILURE_REASON_NATIVE_CRASH:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH;
- case PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK;
- case PackageWatchdog.FAILURE_REASON_APP_CRASH:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH;
- case PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING;
- case PackageWatchdog.FAILURE_REASON_BOOT_LOOP:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING;
- default:
- return WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN;
- }
- }
-
- private static String rollbackTypeToString(int type) {
- switch (type) {
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_INITIATE:
- return "ROLLBACK_INITIATE";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS:
- return "ROLLBACK_SUCCESS";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE:
- return "ROLLBACK_FAILURE";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED:
- return "ROLLBACK_BOOT_TRIGGERED";
- default:
- return "UNKNOWN";
- }
- }
-
- private static String rollbackReasonToString(int reason) {
- switch (reason) {
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH:
- return "REASON_NATIVE_CRASH";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_EXPLICIT_HEALTH_CHECK:
- return "REASON_EXPLICIT_HEALTH_CHECK";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_CRASH:
- return "REASON_APP_CRASH";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_APP_NOT_RESPONDING:
- return "REASON_APP_NOT_RESPONDING";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_NATIVE_CRASH_DURING_BOOT:
- return "REASON_NATIVE_CRASH_DURING_BOOT";
- case WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_BOOT_LOOPING:
- return "REASON_BOOT_LOOP";
- default:
- return "UNKNOWN";
- }
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
deleted file mode 100644
index 29ff7cced897..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/ArrayUtils.java
+++ /dev/null
@@ -1,42 +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 android.util;
-
-import android.annotation.Nullable;
-
-/**
- * Copied over from frameworks/base/core/java/com/android/internal/util/ArrayUtils.java
- *
- * @hide
- */
-public class ArrayUtils {
- private ArrayUtils() { /* cannot be instantiated */ }
-
- /**
- * Checks if given array is null or has zero elements.
- */
- public static boolean isEmpty(@Nullable int[] array) {
- return array == null || array.length == 0;
- }
-
- /**
- * True if the byte array is null or has length 0.
- */
- public static boolean isEmpty(@Nullable byte[] array) {
- return array == null || array.length == 0;
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
deleted file mode 100644
index d60a9b9847ca..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/FileUtils.java
+++ /dev/null
@@ -1,117 +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 android.util;
-
-import android.annotation.Nullable;
-
-import java.io.BufferedInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Bits and pieces copied from hidden API of android.os.FileUtils.
- *
- * @hide
- */
-public class FileUtils {
- /**
- * Read a text file into a String, optionally limiting the length.
- *
- * @param file to read (will not seek, so things like /proc files are OK)
- * @param max length (positive for head, negative of tail, 0 for no limit)
- * @param ellipsis to add of the file was truncated (can be null)
- * @return the contents of the file, possibly truncated
- * @throws IOException if something goes wrong reading the file
- * @hide
- */
- public static @Nullable String readTextFile(@Nullable File file, @Nullable int max,
- @Nullable String ellipsis) throws IOException {
- InputStream input = new FileInputStream(file);
- // wrapping a BufferedInputStream around it because when reading /proc with unbuffered
- // input stream, bytes read not equal to buffer size is not necessarily the correct
- // indication for EOF; but it is true for BufferedInputStream due to its implementation.
- BufferedInputStream bis = new BufferedInputStream(input);
- try {
- long size = file.length();
- if (max > 0 || (size > 0 && max == 0)) { // "head" mode: read the first N bytes
- if (size > 0 && (max == 0 || size < max)) max = (int) size;
- byte[] data = new byte[max + 1];
- int length = bis.read(data);
- if (length <= 0) return "";
- if (length <= max) return new String(data, 0, length);
- if (ellipsis == null) return new String(data, 0, max);
- return new String(data, 0, max) + ellipsis;
- } else if (max < 0) { // "tail" mode: keep the last N
- int len;
- boolean rolled = false;
- byte[] last = null;
- byte[] data = null;
- do {
- if (last != null) rolled = true;
- byte[] tmp = last;
- last = data;
- data = tmp;
- if (data == null) data = new byte[-max];
- len = bis.read(data);
- } while (len == data.length);
-
- if (last == null && len <= 0) return "";
- if (last == null) return new String(data, 0, len);
- if (len > 0) {
- rolled = true;
- System.arraycopy(last, len, last, 0, last.length - len);
- System.arraycopy(data, 0, last, last.length - len, len);
- }
- if (ellipsis == null || !rolled) return new String(last);
- return ellipsis + new String(last);
- } else { // "cat" mode: size unknown, read it all in streaming fashion
- ByteArrayOutputStream contents = new ByteArrayOutputStream();
- int len;
- byte[] data = new byte[1024];
- do {
- len = bis.read(data);
- if (len > 0) contents.write(data, 0, len);
- } while (len == data.length);
- return contents.toString();
- }
- } finally {
- bis.close();
- input.close();
- }
- }
-
- /**
- * Perform an fsync on the given FileOutputStream. The stream at this
- * point must be flushed but not yet closed.
- *
- * @hide
- */
- public static boolean sync(FileOutputStream stream) {
- try {
- if (stream != null) {
- stream.getFD().sync();
- }
- return true;
- } catch (IOException e) {
- }
- return false;
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java b/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
deleted file mode 100644
index 9a24ada8b69a..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/LongArrayQueue.java
+++ /dev/null
@@ -1,188 +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 android.util;
-
-import libcore.util.EmptyArray;
-
-import java.util.NoSuchElementException;
-
-/**
- * Copied from frameworks/base/core/java/android/util/LongArrayQueue.java
- *
- * @hide
- */
-public class LongArrayQueue {
-
- private long[] mValues;
- private int mSize;
- private int mHead;
- private int mTail;
-
- private long[] newUnpaddedLongArray(int num) {
- return new long[num];
- }
- /**
- * Initializes a queue with the given starting capacity.
- *
- * @param initialCapacity the capacity.
- */
- public LongArrayQueue(int initialCapacity) {
- if (initialCapacity == 0) {
- mValues = EmptyArray.LONG;
- } else {
- mValues = newUnpaddedLongArray(initialCapacity);
- }
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Initializes a queue with default starting capacity.
- */
- public LongArrayQueue() {
- this(16);
- }
-
- /** @hide */
- public static int growSize(int currentSize) {
- return currentSize <= 4 ? 8 : currentSize * 2;
- }
-
- private void grow() {
- if (mSize < mValues.length) {
- throw new IllegalStateException("Queue not full yet!");
- }
- final int newSize = growSize(mSize);
- final long[] newArray = newUnpaddedLongArray(newSize);
- final int r = mValues.length - mHead; // Number of elements on and to the right of head.
- System.arraycopy(mValues, mHead, newArray, 0, r);
- System.arraycopy(mValues, 0, newArray, r, mHead);
- mValues = newArray;
- mHead = 0;
- mTail = mSize;
- }
-
- /**
- * Returns the number of elements in the queue.
- */
- public int size() {
- return mSize;
- }
-
- /**
- * Removes all elements from this queue.
- */
- public void clear() {
- mSize = 0;
- mHead = mTail = 0;
- }
-
- /**
- * Adds a value to the tail of the queue.
- *
- * @param value the value to be added.
- */
- public void addLast(long value) {
- if (mSize == mValues.length) {
- grow();
- }
- mValues[mTail] = value;
- mTail = (mTail + 1) % mValues.length;
- mSize++;
- }
-
- /**
- * Removes an element from the head of the queue.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long removeFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final long ret = mValues[mHead];
- mHead = (mHead + 1) % mValues.length;
- mSize--;
- return ret;
- }
-
- /**
- * Returns the element at the given position from the head of the queue, where 0 represents the
- * head of the queue.
- *
- * @param position the position from the head of the queue.
- * @return the element found at the given position.
- * @throws IndexOutOfBoundsException if {@code position} < {@code 0} or
- * {@code position} >= {@link #size()}
- */
- public long get(int position) {
- if (position < 0 || position >= mSize) {
- throw new IndexOutOfBoundsException("Index " + position
- + " not valid for a queue of size " + mSize);
- }
- final int index = (mHead + position) % mValues.length;
- return mValues[index];
- }
-
- /**
- * Returns the element at the head of the queue, without removing it.
- *
- * @return the element at the head of the queue.
- * @throws NoSuchElementException if the queue is empty
- */
- public long peekFirst() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- return mValues[mHead];
- }
-
- /**
- * Returns the element at the tail of the queue.
- *
- * @return the element at the tail of the queue.
- * @throws NoSuchElementException if the queue is empty.
- */
- public long peekLast() {
- if (mSize == 0) {
- throw new NoSuchElementException("Queue is empty!");
- }
- final int index = (mTail == 0) ? mValues.length - 1 : mTail - 1;
- return mValues[index];
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- if (mSize <= 0) {
- return "{}";
- }
-
- final StringBuilder buffer = new StringBuilder(mSize * 64);
- buffer.append('{');
- buffer.append(get(0));
- for (int i = 1; i < mSize; i++) {
- buffer.append(", ");
- buffer.append(get(i));
- }
- buffer.append('}');
- return buffer.toString();
- }
-}
diff --git a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java b/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
deleted file mode 100644
index 488b531c2b8a..000000000000
--- a/packages/CrashRecovery/services/module/java/com/android/util/XmlUtils.java
+++ /dev/null
@@ -1,66 +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 android.util;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-
-/**
- * Bits and pieces copied from hidden API of
- * frameworks/base/core/java/com/android/internal/util/XmlUtils.java
- *
- * @hide
- */
-public class XmlUtils {
-
- /** @hide */
- public static final void beginDocument(XmlPullParser parser, String firstElementName)
- throws XmlPullParserException, IOException {
- int type;
- while ((type = parser.next()) != parser.START_TAG
- && type != parser.END_DOCUMENT) {
- // Do nothing
- }
-
- if (type != parser.START_TAG) {
- throw new XmlPullParserException("No start tag found");
- }
-
- if (!parser.getName().equals(firstElementName)) {
- throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
- + ", expected " + firstElementName);
- }
- }
-
- /** @hide */
- public static boolean nextElementWithin(XmlPullParser parser, int outerDepth)
- throws IOException, XmlPullParserException {
- for (;;) {
- int type = parser.next();
- if (type == XmlPullParser.END_DOCUMENT
- || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) {
- return false;
- }
- if (type == XmlPullParser.START_TAG
- && parser.getDepth() == outerDepth + 1) {
- return true;
- }
- }
- }
-}
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
index 754abb2f76be..96e5892f4d1d 100644
--- a/packages/EasterEgg/AndroidManifest.xml
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -33,7 +33,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:label="@string/app_name">
<!-- Android V easter egg: Daydream version of Landroid
@@ -41,7 +41,7 @@
<service
android:name=".landroid.DreamUniverse"
android:exported="true"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:label="@string/v_egg_name"
android:description="@string/dream_description"
android:enabled="false"
@@ -62,7 +62,7 @@
android:name=".landroid.MainActivity"
android:exported="true"
android:label="@string/u_egg_name"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:configChanges="orientation|screenLayout|screenSize|density"
android:theme="@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen">
<intent-filter>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 000000000000..277df47438e3
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+ <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+ <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 000000000000..17c2b927f4fd
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group>
+ <clip-path
+ android:pathData="M0,0h108v108h-108z"/>
+ <path
+ android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ </group>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 000000000000..4c2932399c1a
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 000000000000..608d5ea6ee48
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:strokeWidth="1"
+ android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M44.5,44.5h19v19h-19z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,35V73"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M73,54L35,54"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
index 8c87c5d4af7b..d56e8b9e8d0e 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/DreamUniverse.kt
@@ -59,7 +59,7 @@ class DreamUniverse : DreamService() {
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
isInteractive = false
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
index 16ec1a933d92..4f77b00b7570 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/MainActivity.kt
@@ -26,7 +26,6 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.withInfiniteAnimationFrameNanos
-import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.forEachGesture
@@ -45,6 +44,7 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.currentRecomposeScope
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -64,7 +64,6 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.toUpperCase
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -75,6 +74,9 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.window.layout.FoldingFeature
import androidx.window.layout.WindowInfoTracker
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
import java.lang.Float.max
import java.lang.Float.min
import java.util.Calendar
@@ -83,9 +85,6 @@ import kotlin.math.absoluteValue
import kotlin.math.floor
import kotlin.math.sqrt
import kotlin.random.Random
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
enum class RandomSeedType {
Fixed,
@@ -139,7 +138,6 @@ fun getDessertCode(): String =
else -> Build.VERSION.RELEASE_OR_CODENAME.replace(Regex("[a-z]*"), "")
}
-
val DEBUG_TEXT = mutableStateOf("Hello Universe")
const val SHOW_DEBUG_TEXT = false
@@ -158,7 +156,7 @@ fun DebugText(text: MutableState<String>) {
}
@Composable
-fun Telemetry(universe: VisibleUniverse) {
+fun Telemetry(universe: Universe) {
var topVisible by remember { mutableStateOf(false) }
var bottomVisible by remember { mutableStateOf(false) }
@@ -180,10 +178,15 @@ fun Telemetry(universe: VisibleUniverse) {
topVisible = true
}
- universe.triggerDraw.value // recompose on every frame
-
val explored = universe.planets.filter { it.explored }
+ // TODO: Narrow the scope of invalidation here to the specific data needed;
+ // the behavior below mimics the previous implementation of a snapshot ticker value
+ val recomposeScope = currentRecomposeScope
+ Telescope(universe) {
+ recomposeScope.invalidate()
+ }
+
BoxWithConstraints(
modifier =
Modifier.fillMaxSize().padding(6.dp).windowInsetsPadding(WindowInsets.safeContent),
@@ -299,7 +302,7 @@ class MainActivity : ComponentActivity() {
enableEdgeToEdge()
- val universe = VisibleUniverse(namer = Namer(resources), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(resources), randomSeed = randomSeed())
if (TEST_UNIVERSE) {
universe.initTest()
@@ -373,7 +376,7 @@ class MainActivity : ComponentActivity() {
@Preview(name = "tablet", device = Devices.TABLET)
@Composable
fun MainActivityPreview() {
- val universe = VisibleUniverse(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
+ val universe = Universe(namer = Namer(Resources.getSystem()), randomSeed = randomSeed())
universe.initTest()
@@ -458,12 +461,12 @@ fun FlightStick(
@Composable
fun Spaaaace(
modifier: Modifier,
- u: VisibleUniverse,
+ u: Universe,
foldState: MutableState<FoldingFeature?> = mutableStateOf(null)
) {
LaunchedEffect(u) {
while (true) withInfiniteAnimationFrameNanos { frameTimeNanos ->
- u.simulateAndDrawFrame(frameTimeNanos)
+ u.step(frameTimeNanos)
}
}
@@ -492,7 +495,7 @@ fun Spaaaace(
val centerFracY: Float by
animateFloatAsState(if (halfFolded && horizontalFold) 0.25f else 0.5f, label = "centerY")
- Canvas(modifier = canvasModifier) {
+ UniverseCanvas(u, canvasModifier) { u ->
drawRect(Colors.Eigengrau, Offset.Zero, size)
val closest = u.closestPlanet()
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
index d14234ec66d9..b8c68818888a 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/Physics.kt
@@ -17,6 +17,8 @@
package com.android.egg.landroid
import android.util.ArraySet
+import androidx.compose.ui.util.fastForEach
+import kotlinx.coroutines.DisposableHandle
import kotlin.random.Random
// artificially speed up or slow down the simulation
@@ -127,6 +129,7 @@ open class Simulator(val randomSeed: Long) {
val rng = Random(randomSeed)
val entities = ArraySet<Entity>(1000)
val constraints = ArraySet<Constraint>(100)
+ private val simStepListeners = mutableListOf<() -> Unit>()
fun add(e: Entity) = entities.add(e)
fun remove(e: Entity) = entities.remove(e)
@@ -169,5 +172,26 @@ open class Simulator(val randomSeed: Long) {
// 3. compute new velocities from updated positions and saved positions
postUpdateAll(dt, localEntities)
+
+ // 4. notify listeners that step is complete
+ simStepListeners.fastForEach { it.invoke() }
+ }
+
+ /**
+ * Register [listener] to be invoked every time the [Simulator] completes one [step].
+ * Use this to enqueue drawing.
+ *
+ * Instead of the usual register()/unregister() pattern, we're going to borrow
+ * [kotlinx.coroutines.DisposableHandle] here. Call [DisposableHandle.dispose] on the return
+ * value to unregister.
+ */
+ fun addSimulationStepListener(listener: () -> Unit): DisposableHandle {
+ // add to listener list
+ simStepListeners += listener
+
+ return DisposableHandle {
+ // on dispose, remove from listener list
+ simStepListeners -= listener
+ }
}
}
diff --git a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
index ed3ebc7bf9a5..c476d5cf0b8b 100644
--- a/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
+++ b/packages/EasterEgg/src/com/android/egg/landroid/VisibleUniverse.kt
@@ -16,19 +16,31 @@
package com.android.egg.landroid
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.RememberObserver
+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.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.PathEffect
import androidx.compose.ui.graphics.PointMode
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.rotateRad
import androidx.compose.ui.graphics.drawscope.scale
import androidx.compose.ui.graphics.drawscope.translate
+import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.invalidateDraw
import androidx.compose.ui.util.lerp
import androidx.core.math.MathUtils.clamp
import com.android.egg.flags.Flags.flagFlag
+import kotlinx.coroutines.DisposableHandle
import java.lang.Float.max
import kotlin.math.exp
import kotlin.math.sqrt
@@ -55,22 +67,108 @@ fun DrawScope.zoom(zoom: Float, block: ZoomedDrawScope.() -> Unit) {
ds.scale(zoom) { block(ds) }
}
-class VisibleUniverse(namer: Namer, randomSeed: Long) : Universe(namer, randomSeed) {
- // Magic variable. Every time we update it, Compose will notice and redraw the universe.
- val triggerDraw = mutableStateOf(0L)
+/**
+ * A device for observing changes to a [Simulator] such as a [Universe].
+ * [observer] will be invoked each time a [Simulator.step] has completed.
+ */
+@Composable
+fun <S : Simulator> Telescope(
+ subject: S,
+ observer: (S) -> Unit
+) {
+ remember(subject) {
+ object : RememberObserver {
+ lateinit var registration: DisposableHandle
+ var currentObserver by mutableStateOf(observer)
+
+ override fun onRemembered() {
+ registration = subject.addSimulationStepListener { currentObserver(subject) }
+ }
+
+ override fun onForgotten() {
+ registration.dispose()
+ }
+
+ override fun onAbandoned() {}
+ }
+ }.currentObserver = observer
+}
+
+fun Modifier.drawUniverse(
+ universe: Universe,
+ draw: DrawScope.(Universe) -> Unit
+): Modifier = this then UniverseElement(universe, draw)
+
+@Composable
+fun UniverseCanvas(
+ universe: Universe,
+ modifier: Modifier = Modifier,
+ draw: DrawScope.(Universe) -> Unit
+) {
+ Spacer(modifier.drawUniverse(universe, draw))
+}
+
+private class UniverseElement(
+ val universe: Universe,
+ val draw: DrawScope.(Universe) -> Unit
+) : ModifierNodeElement<UniverseModifierNode>() {
+ override fun create(): UniverseModifierNode = UniverseModifierNode(universe, draw)
+
+ // Called when a modifier is applied to a Layout whose inputs have changed
+ override fun update(node: UniverseModifierNode) {
+ node.universe = universe
+ node.draw = draw
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
- fun simulateAndDrawFrame(nanos: Long) {
- // By writing this value, Compose will look for functions that read it (like drawZoomed).
- triggerDraw.value = nanos
+ other as UniverseElement
- step(nanos)
+ if (universe != other.universe) return false
+ if (draw != other.draw) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = universe.hashCode()
+ result = 31 * result + draw.hashCode()
+ return result
}
}
-fun ZoomedDrawScope.drawUniverse(universe: VisibleUniverse) {
- with(universe) {
- triggerDraw.value // Please recompose when this value changes.
+private class UniverseModifierNode(
+ universe: Universe,
+ draw: DrawScope.(Universe) -> Unit,
+) : Modifier.Node(), DrawModifierNode {
+ private val universeListener: () -> Unit = { invalidateDraw() }
+ private var removeUniverseListener: DisposableHandle? =
+ universe.addSimulationStepListener(universeListener)
+
+ var universe: Universe = universe
+ set(value) {
+ if (field === value) return
+ removeUniverseListener?.dispose()
+ field = value
+ removeUniverseListener = value.addSimulationStepListener(universeListener)
+ }
+
+ var draw: ContentDrawScope.(Universe) -> Unit = draw
+ set(value) {
+ if (field === value) return
+ field = value
+ invalidateDraw()
+ }
+ override fun ContentDrawScope.draw() {
+ draw(universe)
+ }
+}
+
+fun ZoomedDrawScope.drawUniverse(universe: Universe) {
+ with(universe) {
constraints.forEach {
when (it) {
is Landing -> drawLanding(it)
diff --git a/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.xml
new file mode 100644
index 000000000000..1d57c1617495
--- /dev/null
+++ b/packages/SettingsLib/CardPreference/res/drawable/settingslib_card_preference_background.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.
+-->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorSecondaryContainer" />
+ <corners
+ android:radius="@dimen/settingslib_expressive_radius_extralarge3" />
+ </shape>
+ </item>
+</ripple> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
index 9018baca79e7..4ce106e56822 100644
--- a/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
+++ b/packages/SettingsLib/CardPreference/res/layout/settingslib_expressive_preference_card.xml
@@ -14,9 +14,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<com.google.android.material.card.MaterialCardView
+<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/SettingsLibCardStyle">
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall4">
<LinearLayout
android:id="@+id/card_container"
@@ -24,10 +28,10 @@
android:layout_height="wrap_content"
android:baselineAligned="false"
android:minHeight="@dimen/settingslib_expressive_space_large3"
- android:paddingStart="@dimen/settingslib_expressive_space_small1"
- android:paddingEnd="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_medium1"
android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:background="@drawable/settingslib_card_preference_background">
<LinearLayout
android:id="@+id/icon_frame"
@@ -35,15 +39,13 @@
android:layout_height="wrap_content"
android:minWidth="@dimen/settingslib_expressive_space_medium3"
android:minHeight="@dimen/settingslib_expressive_space_medium3"
- android:gravity="center"
- android:orientation="horizontal">
-
+ android:gravity="center">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/settingslib_expressive_space_medium3"
android:layout_height="@dimen/settingslib_expressive_space_medium3"
- android:scaleType="centerInside"/>
-
+ android:scaleType="centerInside"
+ android:importantForAccessibility="no"/>
</LinearLayout>
<LinearLayout
@@ -54,19 +56,16 @@
android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
android:paddingVertical="@dimen/settingslib_expressive_space_small2"
android:orientation="vertical">
-
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib"/>
-
+ android:textAppearance="@style/TextAppearance.CardTitle.SettingsLib" />
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib"/>
-
+ android:textAppearance="@style/TextAppearance.CardSummary.SettingsLib" />
</LinearLayout>
<ImageView
@@ -75,9 +74,9 @@
android:layout_height="@dimen/settingslib_expressive_space_medium4"
android:padding="@dimen/settingslib_expressive_space_extrasmall4"
android:layout_gravity="center"
+ android:contentDescription="@string/settingslib_dismiss_button_content_description"
android:src="@drawable/settingslib_expressive_icon_close"
- android:background="?android:attr/selectableItemBackground" />
+ android:tint="@color/settingslib_materialColorOnSecondary" />
</LinearLayout>
-
-</com.google.android.material.card.MaterialCardView> \ No newline at end of file
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
index 287b13fa0d50..e7d4a0013896 100644
--- a/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
+++ b/packages/SettingsLib/CardPreference/res/values/styles_expressive.xml
@@ -18,11 +18,11 @@
<resources>
<style name="TextAppearance.CardTitle.SettingsLib"
parent="@style/TextAppearance.SettingsLib.TitleMedium.Emphasized">
- <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
</style>
<style name="TextAppearance.CardSummary.SettingsLib"
parent="@style/TextAppearance.SettingsLib.LabelMedium">
- <item name="android:textColor">@color/settingslib_materialColorOnSecondary</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
</style>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values/strings.xml b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
index c36dcb88b9fe..f3f077edc91d 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/strings.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/strings.xml
@@ -21,4 +21,6 @@
<string name="settingslib_expressive_text_expand">Expand</string>
<!-- text of button to indicate user the textView is collapsable [CHAR LIMIT=NONE] -->
<string name="settingslib_expressive_text_collapse">Collapse</string>
+ <!-- Content description of the dismiss button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="settingslib_dismiss_button_content_description">Dismiss</string>
</resources> \ No newline at end of file
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index bbe08f254283..d94450b1cabd 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -219,3 +219,13 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "adopt_primary_group_management_api"
+ namespace: "cross_device_experiences"
+ description: "Adopt Bluetooth LE broadcast primary group management APIs"
+ bug: "381946931"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
index 4b7cb36f2753..bf86911ee683 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java
@@ -134,6 +134,8 @@ public class CsipDeviceManager {
// Do nothing if GroupId has been assigned
if (!isValidGroupId(cachedDevice.getGroupId())) {
final int newGroupId = getBaseGroupId(cachedDevice.getDevice());
+ log("updateCsipDevices: propose new group id " + newGroupId + " for device "
+ + cachedDevice.getDevice());
// Do nothing if there is no GroupId on Bluetooth device
if (isValidGroupId(newGroupId)) {
cachedDevice.setGroupId(newGroupId);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 7c24df9e9019..ff5e9e657213 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -358,6 +358,9 @@ public class LocalBluetoothProfileManager {
&& mProfile instanceof CsipSetCoordinatorProfile;
if (isAshaProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, hearing aid profile connected, check hisyncid");
+ }
// Check if the HiSyncID has being initialized
if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) {
long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice());
@@ -375,7 +378,9 @@ public class LocalBluetoothProfileManager {
}
if (isHapClientOrLeAudioProfile && newState == BluetoothProfile.STATE_CONNECTED) {
-
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, hap/lea profile connected, check hearing aid info");
+ }
// Checks if both profiles are connected to the device. Hearing aid info need
// to be retrieved from these profiles separately.
if (cachedDevice.isConnectedLeAudioHearingAidDevice()) {
@@ -389,10 +394,16 @@ public class LocalBluetoothProfileManager {
}
if (isCsipProfile && (newState == BluetoothProfile.STATE_CONNECTED)) {
+ if (DEBUG) {
+ Log.d(TAG, "onReceive, csip profile connected, check group id");
+ }
// Check if the GroupID has being initialized
if (cachedDevice.getGroupId() == BluetoothCsipSetCoordinator.GROUP_ID_INVALID) {
final Map<Integer, ParcelUuid> groupIdMap = getCsipSetCoordinatorProfile()
.getGroupUuidMapByDevice(cachedDevice.getDevice());
+ if (DEBUG) {
+ Log.d(TAG, "csip group uuid map = " + groupIdMap);
+ }
if (groupIdMap != null) {
for (Map.Entry<Integer, ParcelUuid> entry: groupIdMap.entrySet()) {
if (entry.getValue().equals(BluetoothUuid.CAP)) {
@@ -431,6 +442,9 @@ public class LocalBluetoothProfileManager {
mProfile.getProfileId());
}
if (needDispatchProfileConnectionState) {
+ if (DEBUG) {
+ Log.d(TAG, "needDispatchProfileConnectionState");
+ }
cachedDevice.refresh();
mEventManager.dispatchProfileConnectionStateChanged(cachedDevice, newState,
mProfile.getProfileId());
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
index 84afb9f7a7e2..a1cf409733fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/AbstractPreferenceController.java
@@ -37,10 +37,10 @@ public abstract class AbstractPreferenceController {
private static final String TAG = "AbstractPrefController";
- protected final Context mContext;
+ protected final @NonNull Context mContext;
private final DevicePolicyManager mDevicePolicyManager;
- public AbstractPreferenceController(Context context) {
+ public AbstractPreferenceController(@NonNull Context context) {
mContext = context;
mDevicePolicyManager =
(DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
index 9aaefe47fda2..58c7907f77de 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/data/repository/FakeZenModeRepository.kt
@@ -37,8 +37,9 @@ class FakeZenModeRepository : ZenModeRepository {
override val globalZenMode: StateFlow<Int>
get() = mutableZenMode.asStateFlow()
- private val mutableModesFlow: MutableStateFlow<List<ZenMode>> =
+ private val mutableModesFlow: MutableStateFlow<List<ZenMode>> by lazy {
MutableStateFlow(listOf(TestModeBuilder.MANUAL_DND))
+ }
override val modes: Flow<List<ZenMode>>
get() = mutableModesFlow.asStateFlow()
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 6681c014f2e0..b0309a8fa5a5 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -454,6 +454,5 @@ 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.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, BOOLEAN_VALIDATOR);
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index bf3afeda448e..0f6311552de9 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -408,77 +408,8 @@ public class SettingsState {
Slog.w(LOG_TAG, "Bulk sync request to acongid failed.");
}
}
-
- if (Flags.disableBulkCompare()) {
- return;
- }
-
- // TOBO(b/312444587): remove the comparison logic after Test Mission 2.
- if (requests == null) {
- Map<String, AconfigdFlagInfo> aconfigdFlagMap =
- AconfigdJavaUtils.listFlagsValueInNewStorage(localSocket);
- compareFlagValueInNewStorage(
- mAconfigDefaultFlags,
- aconfigdFlagMap);
- }
- }
- }
- }
-
- // TODO(b/312444587): remove the comparison logic after Test Mission 2.
- public int compareFlagValueInNewStorage(
- Map<String, AconfigdFlagInfo> defaultFlagMap,
- Map<String, AconfigdFlagInfo> aconfigdFlagMap) {
-
- // Get all defaults from the default map. The mSettings may not contain
- // all flags, since it only contains updated flags.
- int diffNum = 0;
- for (Map.Entry<String, AconfigdFlagInfo> entry : defaultFlagMap.entrySet()) {
- String key = entry.getKey();
- AconfigdFlagInfo flag = entry.getValue();
-
- AconfigdFlagInfo aconfigdFlag = aconfigdFlagMap.get(key);
- if (aconfigdFlag == null) {
- Slog.w(LOG_TAG, String.format("Flag %s is missing from aconfigd", key));
- diffNum++;
- continue;
- }
- String diff = flag.dumpDiff(aconfigdFlag);
- if (!diff.isEmpty()) {
- Slog.w(
- LOG_TAG,
- String.format(
- "Flag %s is different in Settings and aconfig: %s", key, diff));
- diffNum++;
- }
- }
-
- for (String key : aconfigdFlagMap.keySet()) {
- if (defaultFlagMap.containsKey(key)) continue;
- Slog.w(LOG_TAG, String.format("Flag %s is missing from Settings", key));
- diffNum++;
- }
-
- String compareMarkerName = "aconfigd_marker/compare_diff_num";
- synchronized (mLock) {
- Setting markerSetting = mSettings.get(compareMarkerName);
- if (markerSetting == null) {
- markerSetting =
- new Setting(
- compareMarkerName,
- String.valueOf(diffNum),
- false,
- "aconfig",
- "aconfig");
- mSettings.put(compareMarkerName, markerSetting);
}
- markerSetting.value = String.valueOf(diffNum);
- }
-
- if (diffNum == 0) {
- Slog.w(LOG_TAG, "Settings and new storage have same flags.");
}
- return diffNum;
}
@GuardedBy("mLock")
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index cfd27c69032e..4fc3b873aa75 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -100,14 +100,4 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-}
-
-flag {
- name: "disable_bulk_compare"
- namespace: "core_experiments_team_internal"
- description: "Disable bulk comparison between DeviceConfig and aconfig storage."
- bug: "312444587"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9aad5d5f8367..cbdb36fff98c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -689,7 +689,6 @@ public class SettingsBackupTest {
Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD,
Settings.Secure.DEVICE_PAIRED,
Settings.Secure.DIALER_DEFAULT_APPLICATION,
- Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
Settings.Secure.DISABLED_PRINT_SERVICES,
Settings.Secure.DISABLE_SECURE_WINDOWS,
Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 276b206cd6a1..6e7576631147 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -1303,85 +1303,4 @@ public class SettingsStateTest {
assertFalse(flag3.getHasServerOverride());
assertFalse(flag3.getHasLocalOverride());
}
-
- @Test
- @RequiresFlagsDisabled(Flags.FLAG_DISABLE_BULK_COMPARE)
- public void testCompareFlagValueInNewStorage() {
- int configKey = SettingsState.makeKey(SettingsState.SETTINGS_TYPE_CONFIG, 0);
- Object lock = new Object();
- SettingsState settingsState =
- new SettingsState(
- InstrumentationRegistry.getContext(),
- lock,
- mSettingsFile,
- configKey,
- SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED,
- Looper.getMainLooper());
-
- AconfigdFlagInfo defaultFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setDefaultFlagValue("false")
- .setServerFlagValue("true")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- AconfigdFlagInfo expectedFlag1 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag1")
- .setServerFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setIsReadWrite(true)
- .build();
-
- Map<String, AconfigdFlagInfo> aconfigdMap = new HashMap<>();
- Map<String, AconfigdFlagInfo> defaultMap = new HashMap<>();
-
- defaultMap.put("com.android.flags.flag1", defaultFlag1);
- aconfigdMap.put("com.android.flags.flag1", expectedFlag1);
-
- int ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- assertEquals(0, ret);
-
- String value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
-
- AconfigdFlagInfo defaultFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setDefaultFlagValue("false")
- .build();
- defaultMap.put("com.android.flags.flag2", defaultFlag2);
-
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // missing from new storage
- assertEquals(1, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("1", value);
-
- AconfigdFlagInfo expectedFlag2 =
- AconfigdFlagInfo.newBuilder()
- .setPackageName("com.android.flags")
- .setFlagName("flag2")
- .setServerFlagValue("true")
- .setLocalFlagValue("true")
- .setDefaultFlagValue("false")
- .setHasServerOverride(true)
- .setHasLocalOverride(true)
- .build();
- aconfigdMap.put("com.android.flags.flag2", expectedFlag2);
- ret = settingsState.compareFlagValueInNewStorage(defaultMap, aconfigdMap);
- // skip the server and local value comparison when the flag is read_only
- assertEquals(0, ret);
- value =
- settingsState.getSettingLocked("aconfigd_marker/compare_diff_num").getValue();
- assertEquals("0", value);
- }
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0075f85af8ed..b53198d8ae98 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -406,7 +406,7 @@
android:killAfterRestore="false"
android:hardwareAccelerated="true"
android:label="@string/app_label"
- android:icon="@drawable/android15_patch_adaptive"
+ android:icon="@drawable/android16_patch_adaptive"
android:process="com.android.systemui"
android:supportsRtl="true"
android:theme="@style/Theme.SystemUI"
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
index 0f210e7e5e7b..b40a11469172 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
+++ b/packages/SystemUI/accessibility/accessibilitymenu/AndroidManifest.xml
@@ -20,7 +20,10 @@
<uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
<uses-permission android:name="android.permission.MANAGE_USERS"/>
- <application android:supportsRtl="true">
+ <application
+ android:supportsRtl="true"
+ android:allowBackup="true"
+ android:restoreAnyVersion="true">
<service
android:name="com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService"
android:exported="false"
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 1858c80ca901..088ec136f24e 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -23,6 +23,7 @@ package {
default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
default_visibility: [
"//visibility:override",
+ "//frameworks/base/libs/WindowManager/Shell:__subpackages__",
"//frameworks/base/packages/SystemUI:__subpackages__",
"//frameworks/libs/systemui/tracinglib:__subpackages__",
"//frameworks/base/services/accessibility:__subpackages__",
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 153f89284587..b33421d5d4ca 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1514,16 +1514,6 @@ flag {
}
flag {
- name: "sim_pin_talkback_fix_for_double_submit"
- namespace: "systemui"
- description: "The SIM PIN entry screens show the wrong message due"
- bug: "346932439"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "sim_pin_bouncer_reset"
namespace: "systemui"
description: "The SIM PIN bouncer does not close after unlocking"
diff --git a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
index 2e8f92839fa6..4b8610884b05 100644
--- a/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
+++ b/packages/SystemUI/animation/lib/src/com/android/systemui/animation/ViewUIComponent.java
@@ -185,16 +185,24 @@ public class ViewUIComponent implements UIComponent {
return;
}
ViewGroup.LayoutParams params = mView.getLayoutParams();
- if (params == null || params.width == 0 || params.height == 0) {
+ if (params == null) {
// layout pass didn't happen.
logD("draw: skipped - no layout");
return;
}
+
+ final Rect realBounds = getRealBounds();
+ if (realBounds.width() == 0 || realBounds.height() == 0) {
+ // bad bounds.
+ logD("draw: skipped - zero bounds");
+ return;
+ }
+
+
Canvas canvas = mSurface.lockHardwareCanvas();
// Clear the canvas first.
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
if (mVisibleOverride) {
- Rect realBounds = getRealBounds();
Rect renderBounds = getBounds();
canvas.translate(renderBounds.left, renderBounds.top);
canvas.scale(
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
index 311519122312..d08d859ec0d7 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/graphics/DrawInContainer.kt
@@ -19,9 +19,11 @@ package com.android.compose.ui.graphics
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.ContentDrawScope
import androidx.compose.ui.graphics.drawscope.DrawScope
@@ -30,14 +32,14 @@ import androidx.compose.ui.graphics.drawscope.translate
import androidx.compose.ui.graphics.layer.GraphicsLayer
import androidx.compose.ui.graphics.layer.drawLayer
import androidx.compose.ui.layout.LayoutCoordinates
-import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onPlaced
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.modifier.ModifierLocalModifierNode
import androidx.compose.ui.node.DrawModifierNode
+import androidx.compose.ui.node.LayoutAwareModifierNode
import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.node.requireDensity
import androidx.compose.ui.node.requireGraphicsContext
-import androidx.compose.ui.node.requireLayoutCoordinates
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.util.fastForEach
@@ -48,17 +50,7 @@ import androidx.compose.ui.util.fastForEach
* The elements redirected to this container will be drawn above the content of this composable.
*/
fun Modifier.container(state: ContainerState): Modifier {
- return layout { measurable, constraints ->
- val p = measurable.measure(constraints)
- layout(p.width, p.height) {
- val coords = coordinates
- if (coords != null && !isLookingAhead) {
- state.lastCoords = coords
- }
-
- p.place(0, 0)
- }
- }
+ return onPlaced { state.lastOffsetInWindow = it.positionInWindow() }
.drawWithContent {
drawContent()
state.drawInOverlay(this)
@@ -91,7 +83,7 @@ fun Modifier.drawInContainer(
class ContainerState {
private var renderers = mutableStateListOf<LayerRenderer>()
- internal var lastCoords: LayoutCoordinates? = null
+ internal var lastOffsetInWindow by mutableStateOf(Offset.Zero)
internal fun onLayerRendererAttached(renderer: LayerRenderer) {
renderers.add(renderer)
@@ -142,7 +134,8 @@ internal class DrawInContainerNode(
var enabled: () -> Boolean = { true },
zIndex: Float = 0f,
var clipPath: (LayoutDirection, Density) -> Path? = { _, _ -> null },
-) : Modifier.Node(), DrawModifierNode, ModifierLocalModifierNode {
+) : Modifier.Node(), LayoutAwareModifierNode, DrawModifierNode, ModifierLocalModifierNode {
+ private var lastOffsetInWindow by mutableStateOf(Offset.Zero)
var zIndex by mutableFloatStateOf(zIndex)
private inner class LayerWithRenderer(val layer: GraphicsLayer) : LayerRenderer {
@@ -152,11 +145,7 @@ internal class DrawInContainerNode(
override fun drawInOverlay(drawScope: DrawScope) {
if (enabled()) {
with(drawScope) {
- val containerCoords =
- checkNotNull(state.lastCoords) { "container is not placed" }
- val (x, y) =
- requireLayoutCoordinates().positionInWindow() -
- containerCoords.positionInWindow()
+ val (x, y) = lastOffsetInWindow - state.lastOffsetInWindow
val clipPath = clipPath(layoutDirection, requireDensity())
if (clipPath != null) {
clipPath(clipPath) { translate(x, y) { drawLayer(layer) } }
@@ -178,6 +167,10 @@ internal class DrawInContainerNode(
}
}
+ override fun onPlaced(coordinates: LayoutCoordinates) {
+ lastOffsetInWindow = coordinates.positionInWindow()
+ }
+
val layer: GraphicsLayer?
get() = layerWithRenderer?.layer
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 0054a4c899ec..439968590dad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -168,7 +168,7 @@ private fun StandardLayout(viewModel: BouncerSceneContentViewModel, modifier: Mo
LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded
FoldAware(
- modifier = modifier.padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 48.dp),
+ modifier = modifier.padding(top = 92.dp, bottom = 48.dp),
viewModel = viewModel,
aboveFold = {
Column(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 8321238b28b1..3d0354a578f7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
@@ -35,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.geometry.Offset
@@ -45,6 +48,7 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.integerResource
+import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Easings
@@ -212,23 +216,27 @@ fun PatternBouncer(
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var offset: Offset by remember { mutableStateOf(Offset.Zero) }
var scale: Float by remember { mutableStateOf(1f) }
+ // This is the size of the drawing area, in dips.
+ val dotDrawingArea =
+ remember(colCount, rowCount) {
+ DpSize(
+ // Because the width also includes spacing to the left and right of the leftmost and
+ // rightmost dots in the grid and because UX mocks specify the width without that
+ // spacing, the actual width needs to be defined slightly bigger than the UX mock
+ // width.
+ width = (262 * colCount / 2).dp,
+ // Because the height also includes spacing above and below the topmost and
+ // bottommost
+ // dots in the grid and because UX mocks specify the height without that spacing,
+ // the
+ // actual height needs to be defined slightly bigger than the UX mock height.
+ height = (262 * rowCount / 2).dp,
+ )
+ }
- Canvas(
- modifier
- .sysuiResTag("bouncer_pattern_root")
- // Because the width also includes spacing to the left and right of the leftmost and
- // rightmost dots in the grid and because UX mocks specify the width without that
- // spacing, the actual width needs to be defined slightly bigger than the UX mock width.
- .width((262 * colCount / 2).dp)
- // Because the height also includes spacing above and below the topmost and bottommost
- // dots in the grid and because UX mocks specify the height without that spacing, the
- // actual height needs to be defined slightly bigger than the UX mock height.
- .height((262 * rowCount / 2).dp)
- // Need to clip to bounds to make sure that the lines don't follow the input pointer
- // when it leaves the bounds of the dot grid.
- .clipToBounds()
- .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
- .thenIf(isInputEnabled) {
+ Box(
+ modifier =
+ modifier.fillMaxWidth().thenIf(isInputEnabled) {
Modifier.pointerInput(Unit) {
awaitEachGesture {
awaitFirstDown()
@@ -257,105 +265,125 @@ fun PatternBouncer(
inputPosition = change.position
change.position.minus(offset).div(scale).let {
viewModel.onDrag(
- xPx = it.x,
+ xPx =
+ it.x -
+ ((size.width - dotDrawingArea.width.roundToPx()) / 2),
yPx = it.y,
- containerSizePx = size.width,
+ containerSizePx = dotDrawingArea.width.roundToPx(),
)
}
}
}
}
- .motionTestValues {
- entryAnimationCompleted exportAs entryCompleted
- dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
- dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
- dotScalingAnimatables.map { it.value.value } exportAs dotScaling
- }
) {
- gridCoordinates?.let { nonNullCoordinates ->
- val containerSize = nonNullCoordinates.size
- if (containerSize.width <= 0 || containerSize.height <= 0) {
- return@let
- }
+ Canvas(
+ Modifier.sysuiResTag("bouncer_pattern_root")
+ .width(dotDrawingArea.width)
+ .height(dotDrawingArea.height)
+ // Need to clip to bounds to make sure that the lines don't follow the input pointer
+ // when it leaves the bounds of the dot grid.
+ .clipToBounds()
+ .align(Alignment.Center)
+ .onGloballyPositioned { coordinates -> gridCoordinates = coordinates }
+ .motionTestValues {
+ entryAnimationCompleted exportAs entryCompleted
+ dotAppearFadeInAnimatables.map { it.value.value } exportAs dotAppearFadeIn
+ dotAppearMoveUpAnimatables.map { it.value.value } exportAs dotAppearMoveUp
+ dotScalingAnimatables.map { it.value.value } exportAs dotScaling
+ }
+ ) {
+ gridCoordinates?.let { nonNullCoordinates ->
+ val containerSize = nonNullCoordinates.size
+ if (containerSize.width <= 0 || containerSize.height <= 0) {
+ return@let
+ }
- val horizontalSpacing = containerSize.width.toFloat() / colCount
- val verticalSpacing = containerSize.height.toFloat() / rowCount
- val spacing = min(horizontalSpacing, verticalSpacing)
- val horizontalOffset =
- offset(
- availableSize = containerSize.width,
- spacingPerDot = spacing,
- dotCount = colCount,
- isCentered = true,
- )
- val verticalOffset =
- offset(
- availableSize = containerSize.height,
- spacingPerDot = spacing,
- dotCount = rowCount,
- isCentered = centerDotsVertically,
- )
- offset = Offset(horizontalOffset, verticalOffset)
- scale = (colCount * spacing) / containerSize.width
+ val horizontalSpacing = containerSize.width.toFloat() / colCount
+ val verticalSpacing = containerSize.height.toFloat() / rowCount
+ val spacing = min(horizontalSpacing, verticalSpacing)
+ val horizontalOffset =
+ offset(
+ availableSize = containerSize.width,
+ spacingPerDot = spacing,
+ dotCount = colCount,
+ isCentered = true,
+ )
+ val verticalOffset =
+ offset(
+ availableSize = containerSize.height,
+ spacingPerDot = spacing,
+ dotCount = rowCount,
+ isCentered = centerDotsVertically,
+ )
+ offset = Offset(horizontalOffset, verticalOffset)
+ scale = (colCount * spacing) / containerSize.width
- if (isAnimationEnabled) {
- // Draw lines between dots.
- selectedDots.forEachIndexed { index, dot ->
- if (index > 0) {
- val previousDot = selectedDots[index - 1]
- val lineFadeOutAnimationProgress =
- lineFadeOutAnimatables[previousDot]!!.value
- val startLerp = 1 - lineFadeOutAnimationProgress
- val from =
- pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
- val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
- val lerpedFrom =
- Offset(
- x = from.x + (to.x - from.x) * startLerp,
- y = from.y + (to.y - from.y) * startLerp,
+ if (isAnimationEnabled) {
+ // Draw lines between dots.
+ selectedDots.forEachIndexed { index, dot ->
+ if (index > 0) {
+ val previousDot = selectedDots[index - 1]
+ val lineFadeOutAnimationProgress =
+ lineFadeOutAnimatables[previousDot]!!.value
+ val startLerp = 1 - lineFadeOutAnimationProgress
+ val from =
+ pixelOffset(previousDot, spacing, horizontalOffset, verticalOffset)
+ val to = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+ val lerpedFrom =
+ Offset(
+ x = from.x + (to.x - from.x) * startLerp,
+ y = from.y + (to.y - from.y) * startLerp,
+ )
+ drawLine(
+ start = lerpedFrom,
+ end = to,
+ cap = StrokeCap.Round,
+ alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
)
- drawLine(
- start = lerpedFrom,
- end = to,
- cap = StrokeCap.Round,
- alpha = lineFadeOutAnimationProgress * lineAlpha(spacing),
- color = lineColor,
- strokeWidth = lineStrokeWidth,
- )
+ }
}
- }
- // Draw the line between the most recently-selected dot and the input pointer
- // position.
- inputPosition?.let { lineEnd ->
- currentDot?.let { dot ->
- val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
- val lineLength =
- sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
- drawLine(
- start = from,
- end = lineEnd,
- cap = StrokeCap.Round,
- alpha = lineAlpha(spacing, lineLength),
- color = lineColor,
- strokeWidth = lineStrokeWidth,
- )
+ // Draw the line between the most recently-selected dot and the input pointer
+ // position.
+ inputPosition?.let { lineEnd ->
+ currentDot?.let { dot ->
+ val from = pixelOffset(dot, spacing, horizontalOffset, verticalOffset)
+ val lineLength =
+ sqrt((from.y - lineEnd.y).pow(2) + (from.x - lineEnd.x).pow(2))
+ drawLine(
+ start = from,
+ end = lineEnd,
+ cap = StrokeCap.Round,
+ alpha = lineAlpha(spacing, lineLength),
+ color = lineColor,
+ strokeWidth = lineStrokeWidth,
+ )
+ }
}
}
- }
- // Draw each dot on the grid.
- dots.forEach { dot ->
- val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
- val appearOffset =
- (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
- drawCircle(
- center =
- pixelOffset(dot, spacing, horizontalOffset, verticalOffset + appearOffset),
- color =
- dotColor.copy(alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value),
- radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
- )
+ // Draw each dot on the grid.
+ dots.forEach { dot ->
+ val initialOffset = checkNotNull(dotAppearMaxOffsetPixels[dot])
+ val appearOffset =
+ (1 - checkNotNull(dotAppearMoveUpAnimatables[dot]).value) * initialOffset
+ drawCircle(
+ center =
+ pixelOffset(
+ dot,
+ spacing,
+ horizontalOffset,
+ verticalOffset + appearOffset,
+ ),
+ color =
+ dotColor.copy(
+ alpha = checkNotNull(dotAppearFadeInAnimatables[dot]).value
+ ),
+ radius = dotRadius * checkNotNull(dotScalingAnimatables[dot]).value,
+ )
+ }
}
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
index f2edec657cd4..3ae50369e9e3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalTouchableSurface.kt
@@ -27,9 +27,14 @@ import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.input.pointer.motionEventSpy
-import androidx.compose.ui.semantics.hideFromAccessibility
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.semantics.CustomAccessibilityAction
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.customActions
import androidx.compose.ui.semantics.semantics
+import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
+import com.android.systemui.res.R
@OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
@Composable
@@ -38,15 +43,38 @@ fun CommunalTouchableSurface(
modifier: Modifier = Modifier,
content: @Composable BoxScope.() -> Unit,
) {
-
+ val context = LocalContext.current
val interactionSource = remember { MutableInteractionSource() }
Box(
modifier =
modifier
- // The touchable surface is hidden for accessibility because these actions are
- // already provided through custom accessibility actions.
- .semantics { hideFromAccessibility() }
+ .semantics {
+ contentDescription =
+ context.getString(
+ R.string.accessibility_content_description_for_communal_hub
+ )
+ customActions =
+ listOf(
+ CustomAccessibilityAction(
+ context.getString(
+ R.string.accessibility_action_label_close_communal_hub
+ )
+ ) {
+ viewModel.changeScene(
+ CommunalScenes.Blank,
+ "closed by accessibility",
+ )
+ true
+ },
+ CustomAccessibilityAction(
+ context.getString(R.string.accessibility_action_label_edit_widgets)
+ ) {
+ viewModel.onOpenWidgetEditor()
+ true
+ },
+ )
+ }
.combinedClickable(
onLongClick = viewModel::onLongClick,
onClick = viewModel::onClick,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
index f7ce2153b0ec..7f7273d710a1 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -75,6 +75,7 @@ import com.android.compose.animation.Expandable
import com.android.compose.animation.scene.SceneScope
import com.android.compose.modifiers.fadingBackground
import com.android.compose.theme.colorAttr
+import com.android.systemui.Flags.notificationShadeBlur
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
@@ -163,14 +164,16 @@ fun FooterActions(
}
}
- val backgroundColor = colorAttr(R.attr.underSurface)
+ val backgroundColor =
+ if (!notificationShadeBlur()) colorAttr(R.attr.underSurface) else Color.Transparent
+ val backgroundAlphaValue = if (!notificationShadeBlur()) backgroundAlpha::value else ({ 0f })
val contentColor = MaterialTheme.colorScheme.onSurface
val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
val backgroundModifier =
- remember(backgroundColor, backgroundAlpha, backgroundTopRadius) {
+ remember(backgroundColor, backgroundAlphaValue, backgroundTopRadius) {
Modifier.fadingBackground(
backgroundColor,
- backgroundAlpha::value,
+ backgroundAlphaValue,
RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
)
}
@@ -305,7 +308,8 @@ private fun NumberButton(
) {
Box(Modifier.size(40.dp)) {
Box(
- Modifier.fillMaxSize()
+ Modifier
+ .fillMaxSize()
.clip(CircleShape)
.indication(interactionSource, LocalIndication.current)
) {
@@ -333,7 +337,9 @@ private fun NewChangesDot(modifier: Modifier = Modifier) {
val contentDescription = stringResource(R.string.fgs_dot_content_description)
val color = MaterialTheme.colorScheme.tertiary
- Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
+ Canvas(modifier
+ .size(12.dp)
+ .semantics { this.contentDescription = contentDescription }) {
drawCircle(color)
}
}
@@ -362,7 +368,9 @@ private fun TextButton(
Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)),
verticalAlignment = Alignment.CenterVertically,
) {
- Icon(icon, Modifier.padding(end = 12.dp).size(20.dp))
+ Icon(icon, Modifier
+ .padding(end = 12.dp)
+ .size(20.dp))
Text(
text,
@@ -383,7 +391,9 @@ private fun TextButton(
Icon(
painterResource(com.android.internal.R.drawable.ic_chevron_end),
contentDescription = null,
- Modifier.padding(start = 8.dp).size(20.dp),
+ Modifier
+ .padding(start = 8.dp)
+ .size(20.dp),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index f052e60246d2..50bae8a094ad 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -31,10 +31,14 @@ import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentScope
@@ -59,6 +63,8 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.CollapsedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
@@ -96,13 +102,37 @@ constructor(
override fun ContentScope.Content(modifier: Modifier) {
val viewModel =
rememberViewModel("QuickSettingsShadeOverlay") { contentViewModelFactory.create() }
+ val panelCornerRadius =
+ with(LocalDensity.current) { OverlayShade.Dimensions.PanelCornerRadius.toPx().toInt() }
+
+ // set the bounds to null when the QuickSettings overlay disappears
+ DisposableEffect(Unit) { onDispose { viewModel.onPanelShapeChanged(null) } }
OverlayShade(
panelAlignment = Alignment.TopEnd,
modifier = modifier,
onScrimClicked = viewModel::onScrimClicked,
) {
- Column {
+ Column(
+ modifier =
+ Modifier.onPlaced { coordinates ->
+ val boundsInWindow = coordinates.boundsInWindow()
+ val shadeScrimBounds =
+ ShadeScrimBounds(
+ left = boundsInWindow.left,
+ top = boundsInWindow.top,
+ right = boundsInWindow.right,
+ bottom = boundsInWindow.bottom,
+ )
+ val shape =
+ ShadeScrimShape(
+ bounds = shadeScrimBounds,
+ topRadius = 0,
+ bottomRadius = panelCornerRadius,
+ )
+ viewModel.onPanelShapeChanged(shape)
+ }
+ ) {
if (viewModel.showHeader) {
CollapsedShadeHeader(
viewModelFactory = viewModel.shadeHeaderViewModelFactory,
@@ -112,7 +142,6 @@ constructor(
statusBarIconController = statusBarIconController,
)
}
-
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt
index 2f2f3a35dbaa..552679224bca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/SensorLocation.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.shared.model
+package com.android.systemui.shared.customization.data
/**
* Provides current sensor location information in the current screen resolution [scale].
@@ -26,18 +26,40 @@ data class SensorLocation(
private val naturalCenterX: Int,
private val naturalCenterY: Int,
private val naturalRadius: Int,
- private val scale: Float = 1f
+ private val scale: Float = 1f,
) {
val centerX: Float
get() {
return naturalCenterX * scale
}
+
val centerY: Float
get() {
return naturalCenterY * scale
}
+
val radius: Float
get() {
return naturalRadius * scale
}
+
+ fun encode(): String {
+ return floatArrayOf(
+ naturalCenterX.toFloat(),
+ naturalCenterY.toFloat(),
+ naturalRadius.toFloat(),
+ scale,
+ )
+ .joinToString(DELIMITER)
+ }
+
+ companion object {
+
+ private const val DELIMITER: String = ","
+
+ fun decode(encoded: String): SensorLocation {
+ val array = encoded.split(DELIMITER).map { it.toFloat() }.toFloatArray()
+ return SensorLocation(array[0].toInt(), array[1].toInt(), array[2].toInt(), array[3])
+ }
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
index 48af2d9f5542..caa6636bde03 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt
@@ -80,13 +80,6 @@ interface CustomizationProviderClient {
fun observeFlags(): Flow<List<Flag>>
/**
- * Returns [Flow] for observing the variables from the System UI.
- *
- * @see [queryRuntimeValues]
- */
- fun observeRuntimeValues(): Flow<Bundle>
-
- /**
* Returns all available affordances supported by the device, regardless of current slot
* placement.
*/
@@ -291,6 +284,9 @@ class CustomizationProviderClientImpl(
Contract.RuntimeValuesTable.KEY_IS_SHADE_LAYOUT_WIDE -> {
putBoolean(name, cursor.getInt(valueColumnIndex) == 1)
}
+ Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION -> {
+ putString(name, cursor.getString(valueColumnIndex))
+ }
}
}
}
@@ -307,10 +303,6 @@ class CustomizationProviderClientImpl(
return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
}
- override fun observeRuntimeValues(): Flow<Bundle> {
- return observeUri(Contract.RuntimeValuesTable.URI).map { queryRuntimeValues() }
- }
-
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return withContext(backgroundDispatcher) {
context.contentResolver
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index cb167eddcea9..2934f070b05f 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -19,6 +19,7 @@ package com.android.systemui.shared.customization.data.content
import android.content.ContentResolver
import android.net.Uri
+import com.android.systemui.shared.customization.data.SensorLocation
/** Contract definitions for querying content about keyguard quick affordances. */
object CustomizationProviderContract {
@@ -213,6 +214,11 @@ object CustomizationProviderContract {
* be as wide as the entire screen.
*/
const val KEY_IS_SHADE_LAYOUT_WIDE = "is_shade_layout_wide"
+ /**
+ * This key corresponds to a String value, representing the string form of [SensorLocation],
+ * which contains the information of the UDFPS location.
+ */
+ const val KEY_UDFPS_LOCATION = "udfps_location"
object Columns {
/** String. Unique ID for the value. */
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
index 47c5bda93c0e..70d17820a12c 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt
@@ -108,10 +108,6 @@ class FakeCustomizationProviderClient(
return flags.asStateFlow()
}
- override fun observeRuntimeValues(): Flow<Bundle> {
- return runtimeValues.asStateFlow()
- }
-
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return affordances.value
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 4d1660e71c0a..e26e19d27417 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -79,8 +79,6 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
@Mock
private LatencyTracker mLatencyTracker;
@Mock
- private LiftToActivateListener mLiftToactivateListener;
- @Mock
private EmergencyButtonController mEmergencyButtonController;
private FalsingCollector mFalsingCollector = new FalsingCollectorFake();
@Mock
@@ -122,7 +120,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_REVAMPED_BOUNCER_MESSAGES);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
+ mKeyguardMessageAreaControllerFactory, mLatencyTracker,
mEmergencyButtonController, mFalsingCollector, featureFlags,
mSelectedUserInteractor, keyguardKeyboardInteractor, mBouncerHapticPlayer,
mUserActivityNotifier) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4d2a6d9bd57a..142a2868ec14 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -90,8 +90,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mLatencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
-
@Mock private val mEmergencyButtonController: EmergencyButtonController? = null
private val falsingCollector: FalsingCollector = FalsingCollectorFake()
private val keyguardKeyboardInteractor = KeyguardKeyboardInteractor(FakeKeyboardRepository())
@@ -147,7 +145,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
mKeyguardSecurityCallback,
keyguardMessageAreaControllerFactory,
mLatencyTracker,
- liftToActivateListener,
mEmergencyButtonController,
falsingCollector,
postureController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index 9cd52153eff6..c751a7db51dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -63,7 +63,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
@Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -100,7 +99,6 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
keyguardSecurityCallback,
messageAreaControllerFactory,
latencyTracker,
- liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 3c229975eef5..c34682551eda 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -57,7 +57,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
@Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
@Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock private lateinit var latencyTracker: LatencyTracker
- @Mock private lateinit var liftToActivateListener: LiftToActivateListener
@Mock private lateinit var telephonyManager: TelephonyManager
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var emergencyButtonController: EmergencyButtonController
@@ -95,7 +94,6 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
keyguardSecurityCallback,
messageAreaControllerFactory,
latencyTracker,
- liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
new file mode 100644
index 000000000000..fbb0fee2419c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.bouncer.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.SystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardBouncerRepositoryTest : SysuiTestCase() {
+
+ @Mock private lateinit var systemClock: SystemClock
+ @Mock private lateinit var bouncerLogger: TableLogBuffer
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ lateinit var underTest: KeyguardBouncerRepository
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ underTest =
+ object :
+ KeyguardBouncerRepositoryImpl(
+ systemClock,
+ testScope.backgroundScope,
+ bouncerLogger,
+ ) {
+ override fun isDebuggable(): Boolean = true
+ }
+ }
+
+ @Test
+ fun changingFlowValueTriggersLogging() =
+ testScope.runTest {
+ underTest.setPrimaryShow(true)
+ runCurrent()
+ Mockito.verify(bouncerLogger)
+ .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
+ }
+
+ @Test
+ fun primaryStartDisappearAnimation() =
+ testScope.runTest {
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+ underTest.setPrimaryStartDisappearAnimation(Runnable {})
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isTrue()
+
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(underTest.isPrimaryBouncerStartingDisappearAnimation()).isFalse()
+
+ val disappearFlow by collectValues(underTest.primaryBouncerStartingDisappearAnimation)
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(disappearFlow[0]).isNull()
+
+ // Now issue two in a row to make sure one is not dropped
+ underTest.setPrimaryStartDisappearAnimation(Runnable {})
+ underTest.setPrimaryStartDisappearAnimation(null)
+ assertThat(disappearFlow[1]).isNotNull()
+ assertThat(disappearFlow[2]).isNull()
+ }
+}
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 d5e1fae215c7..c1feca29906a 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
@@ -105,7 +105,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
mSelectedUserInteractor,
faceAuthInteractor,
)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
whenever(bouncerView.delegate).thenReturn(bouncerViewDelegate)
resources = context.orCreateTestableResources
@@ -199,7 +199,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_fullyShown() {
whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
@@ -208,7 +207,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_fullyHidden() {
whenever(repository.panelExpansionAmount.value).thenReturn(0.5f)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryShow(false)
verify(falsingCollector).onBouncerHidden()
@@ -307,7 +305,6 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
fun testIsFullShowing() {
whenever(repository.primaryBouncerShow.value).thenReturn(true)
whenever(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(underTest.isFullyShowing()).isTrue()
whenever(repository.primaryBouncerShow.value).thenReturn(false)
assertThat(underTest.isFullyShowing()).isFalse()
@@ -333,9 +330,9 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testIsAnimatingAway() {
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(true)
assertThat(underTest.isAnimatingAway()).isTrue()
- whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
+ whenever(repository.isPrimaryBouncerStartingDisappearAnimation()).thenReturn(false)
assertThat(underTest.isAnimatingAway()).isFalse()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
index ca7e2032be93..26859b6e10f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/viewmodel/CommunalUserActionsViewModelTest.kt
@@ -190,11 +190,8 @@ class CommunalUserActionsViewModelTest : SysuiTestCase() {
lockDevice()
}
- if (shadeMode == ShadeMode.Dual) {
- assertThat(DualShade.isEnabled).isTrue()
- } else {
- assertThat(DualShade.isEnabled).isFalse()
- kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+ if (shadeMode !is ShadeMode.Dual) {
+ kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
}
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
index d6daa794c45b..e19ea365fa1f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/ui/viewmodel/DreamUserActionsViewModelTest.kt
@@ -316,11 +316,8 @@ class DreamUserActionsViewModelTest : SysuiTestCase() {
lockDevice()
}
- if (shadeMode == ShadeMode.Dual) {
- assertThat(DualShade.isEnabled).isTrue()
- } else {
- assertThat(DualShade.isEnabled).isFalse()
- kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode == ShadeMode.Split)
+ if (shadeMode !is ShadeMode.Dual) {
+ kosmos.fakeShadeRepository.setShadeLayoutWide(shadeMode is ShadeMode.Split)
}
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
index 79556baed067..fecd8c3cacca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/InternetTileNewImplTest.kt
@@ -26,11 +26,9 @@ import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
-import com.android.systemui.Flags
import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.flags.setFlagValue
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -42,7 +40,6 @@ import com.android.systemui.qs.logging.QSLogger
import com.android.systemui.qs.tiles.dialog.InternetDialogManager
import com.android.systemui.qs.tiles.dialog.WifiStateWorker
import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.connectivity.AccessPointController
import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun
@@ -65,6 +62,7 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -150,11 +148,18 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
)
underTest.initialize()
+
underTest.setListening(Object(), true)
looper.processAllMessages()
}
+ @After
+ fun tearDown() {
+ underTest.destroy()
+ looper.processAllMessages()
+ }
+
@Test
fun noDefaultConnection_noNetworkAvailable() =
testScope.runTest {
@@ -272,33 +277,37 @@ class InternetTileNewImplTest(flags: FlagsParameterization) : SysuiTestCase() {
underTest.click(null)
looper.processAllMessages()
- verify(dialogManager, times(1)).create(
- aboveStatusBar = true,
- accessPointController.canConfigMobileData(),
- accessPointController.canConfigWifi(),
- null,
- )
+ verify(dialogManager, times(1))
+ .create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
}
@Test
@EnableFlags(
- value = [
- QsDetailedView.FLAG_NAME,
- FLAG_SCENE_CONTAINER,
- KeyguardWmStateRefactor.FLAG_NAME,
- NotificationThrottleHun.FLAG_NAME,
- DualShade.FLAG_NAME]
+ value =
+ [
+ QsDetailedView.FLAG_NAME,
+ FLAG_SCENE_CONTAINER,
+ KeyguardWmStateRefactor.FLAG_NAME,
+ NotificationThrottleHun.FLAG_NAME,
+ DualShade.FLAG_NAME,
+ ]
)
fun click_withQsDetailedViewEnabled() {
underTest.click(null)
looper.processAllMessages()
- verify(dialogManager, times(0)).create(
- aboveStatusBar = true,
- accessPointController.canConfigMobileData(),
- accessPointController.canConfigWifi(),
- null,
- )
+ verify(dialogManager, times(0))
+ .create(
+ aboveStatusBar = true,
+ accessPointController.canConfigMobileData(),
+ accessPointController.canConfigWifi(),
+ null,
+ )
}
companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index c7da03dbbf30..497e33536b7f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -46,6 +46,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executors
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -123,6 +124,12 @@ class RecordIssueTileTest : SysuiTestCase() {
)
}
+ @After
+ fun teardown() {
+ tile.destroy()
+ testableLooper.processAllMessages()
+ }
+
@Test
fun qsTileUi_shouldLookCorrect_whenInactive() {
whenever(issueRecordingState.isRecording).thenReturn(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
index 7ab8ab93c024..dce110201e1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelTest.kt
@@ -39,6 +39,9 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.shared.flag.DualShade
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -143,6 +146,23 @@ class QuickSettingsShadeOverlayContentViewModelTest : SysuiTestCase() {
assertThat(underTest.showHeader).isFalse()
}
+ @Test
+ fun onPanelShapeChanged() =
+ testScope.runTest {
+ val actual by
+ collectLastValue(kosmos.notificationStackAppearanceInteractor.qsPanelShape)
+ val expected =
+ ShadeScrimShape(
+ bounds = ShadeScrimBounds(left = 10f, top = 0f, right = 710f, bottom = 600f),
+ topRadius = 0,
+ bottomRadius = 100,
+ )
+
+ underTest.onPanelShapeChanged(expected)
+
+ assertThat(expected).isEqualTo(actual)
+ }
+
private fun TestScope.lockDevice() {
val currentScene by collectLastValue(sceneInteractor.currentScene)
kosmos.powerInteractor.setAsleepForTest()
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 51f056aa18da..cb7267b2c34c 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
@@ -1508,10 +1508,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
- fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
+ fun collectFalsingSignals_screenOnAndOff() =
testScope.runTest {
- kosmos.fakeKeyguardRepository.setAodAvailable(false)
- runCurrent()
prepareState(
initialSceneKey = Scenes.Lockscreen,
authenticationMethod = AuthenticationMethodModel.Pin,
@@ -1556,53 +1554,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
- fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
- testScope.runTest {
- kosmos.fakeKeyguardRepository.setAodAvailable(true)
- runCurrent()
- prepareState(
- initialSceneKey = Scenes.Lockscreen,
- authenticationMethod = AuthenticationMethodModel.Pin,
- isDeviceUnlocked = false,
- )
- underTest.start()
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
-
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
-
- powerInteractor.setAsleepForTest()
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
-
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_TAP)
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
-
- powerInteractor.setAsleepForTest()
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
-
- powerInteractor.setAwakeForTest(reason = PowerManager.WAKE_REASON_POWER_BUTTON)
- runCurrent()
- verify(falsingCollector, never()).onScreenTurningOn()
- verify(falsingCollector, never()).onScreenOnFromTouch()
- verify(falsingCollector, never()).onScreenOff()
- }
-
- @Test
fun collectFalsingSignals_bouncerVisibility() =
testScope.runTest {
prepareState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
index fd9f5f02ee62..20dfd3e11947 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicyTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.shade.display
import android.view.Display
import android.view.Display.TYPE_EXTERNAL
+import android.view.MotionEvent
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -28,11 +29,16 @@ import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.test.runTest
import org.junit.runner.RunWith
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -50,9 +56,19 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
keyguardRepository,
testScope.backgroundScope,
shadeOnDefaultDisplayWhenLocked = shadeOnDefaultDisplayWhenLocked,
+ shadeInteractor = { kosmos.shadeInteractor },
+ { kosmos.qsElement },
+ { kosmos.notificationElement },
)
}
+ private fun createMotionEventForDisplay(displayId: Int, xCoordinate: Float = 0f): MotionEvent {
+ return mock<MotionEvent> {
+ on { getX() } doReturn xCoordinate
+ on { getDisplayId() } doReturn displayId
+ }
+ }
+
@Test
fun displayId_defaultToDefaultDisplay() {
val underTest = createUnderTest()
@@ -67,7 +83,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
}
@@ -79,7 +95,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayIds by collectValues(underTest.displayId)
assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
// Never set, as 2 was not a display according to the repository.
assertThat(displayIds).isEqualTo(listOf(Display.DEFAULT_DISPLAY))
@@ -92,7 +108,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -108,7 +124,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -124,7 +140,7 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
val displayId by collectLastValue(underTest.displayId)
displayRepository.addDisplays(display(id = 2, type = TYPE_EXTERNAL))
- underTest.onStatusBarTouched(2)
+ underTest.onStatusBarTouched(createMotionEventForDisplay(2), STATUS_BAR_WIDTH)
assertThat(displayId).isEqualTo(2)
@@ -136,4 +152,48 @@ class StatusBarTouchShadeDisplayPolicyTest : SysuiTestCase() {
assertThat(displayId).isEqualTo(2)
}
+
+ @Test
+ fun onStatusBarTouched_leftSide_intentSetToNotifications() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+ STATUS_BAR_WIDTH,
+ )
+
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+ }
+
+ @Test
+ fun onStatusBarTouched_rightSide_intentSetToQs() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.95f),
+ STATUS_BAR_WIDTH,
+ )
+
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.qsElement)
+ }
+
+ @Test
+ fun onStatusBarTouched_nullAfterConsumed() =
+ testScope.runTest {
+ val underTest = createUnderTest(shadeOnDefaultDisplayWhenLocked = true)
+
+ underTest.onStatusBarTouched(
+ createMotionEventForDisplay(2, STATUS_BAR_WIDTH * 0.1f),
+ STATUS_BAR_WIDTH,
+ )
+ assertThat(underTest.consumeExpansionIntent()).isEqualTo(kosmos.notificationElement)
+
+ assertThat(underTest.consumeExpansionIntent()).isNull()
+ }
+
+ companion object {
+ private const val STATUS_BAR_WIDTH = 100
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
index 58396e7cef82..8aa8a50afcd4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractorTest.kt
@@ -22,8 +22,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.NotificationElement
-import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractorImpl.QSElement
import com.android.systemui.shade.shadeTestUtil
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -52,7 +50,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() {
val element = currentlyExpandedElement.value
- assertThat(element).isInstanceOf(QSElement::class.java)
+ assertThat(element).isInstanceOf(QSShadeElement::class.java)
}
@Test
@@ -62,7 +60,7 @@ class ShadeExpandedStateInteractorTest : SysuiTestCase() {
val element = underTest.currentlyExpandedElement.value
- assertThat(element).isInstanceOf(NotificationElement::class.java)
+ assertThat(element).isInstanceOf(NotificationShadeElement::class.java)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt
new file mode 100644
index 000000000000..526fd45533c7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/customization/data/SensorLocationTest.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.shared.customization.data
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SensorLocationTest : SysuiTestCase() {
+
+ @Test
+ fun encodeAndDecode() {
+ val sensorLocation = SensorLocation(640, 2068, 117, 0.75f)
+
+ assertThat(SensorLocation.decode(sensorLocation.encode())).isEqualTo(sensorLocation)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index 1df2553d0eb8..c3547bc5cc9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -74,7 +74,8 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
testScope.runTest {
val radius = MutableStateFlow(32)
val leftOffset = MutableStateFlow(0)
- val shape by collectLastValue(scrollViewModel.shadeScrimShape(radius, leftOffset))
+ val shape by
+ collectLastValue(scrollViewModel.notificationScrimShape(radius, leftOffset))
// When: receive scrim bounds
placeholderViewModel.onScrimBoundsChanged(
@@ -87,7 +88,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
bounds =
ShadeScrimBounds(left = 0f, top = 200f, right = 100f, bottom = 550f),
topRadius = 32,
- bottomRadius = 0
+ bottomRadius = 0,
)
)
@@ -104,7 +105,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
bounds =
ShadeScrimBounds(left = 10f, top = 200f, right = 100f, bottom = 550f),
topRadius = 24,
- bottomRadius = 0
+ bottomRadius = 0,
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
index 06a2c5af2986..66ccf1822e21 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractorTest.kt
@@ -25,6 +25,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -40,28 +41,39 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
private val underTest = kosmos.notificationStackAppearanceInteractor
@Test
- fun stackBounds() =
+ fun stackNotificationScrimBounds() =
testScope.runTest {
- val stackBounds by collectLastValue(underTest.shadeScrimBounds)
+ val stackBounds by collectLastValue(underTest.notificationShadeScrimBounds)
- val bounds1 =
- ShadeScrimBounds(
- top = 100f,
- bottom = 200f,
- )
- underTest.setShadeScrimBounds(bounds1)
+ val bounds1 = ShadeScrimBounds(top = 100f, bottom = 200f)
+ underTest.setNotificationShadeScrimBounds(bounds1)
assertThat(stackBounds).isEqualTo(bounds1)
- val bounds2 =
- ShadeScrimBounds(
- top = 200f,
- bottom = 300f,
- )
- underTest.setShadeScrimBounds(bounds2)
+ val bounds2 = ShadeScrimBounds(top = 200f, bottom = 300f)
+ underTest.setNotificationShadeScrimBounds(bounds2)
assertThat(stackBounds).isEqualTo(bounds2)
}
@Test
+ fun setQsPanelShape() =
+ testScope.runTest {
+ val actual by collectLastValue(underTest.qsPanelShape)
+
+ val expected1 =
+ ShadeScrimShape(
+ bounds = ShadeScrimBounds(top = 0f, bottom = 100f),
+ topRadius = 0,
+ bottomRadius = 10,
+ )
+ underTest.setQsPanelShape(expected1)
+ assertThat(actual).isEqualTo(expected1)
+
+ val expected2 = expected1.copy(topRadius = 10)
+ underTest.setQsPanelShape(expected2)
+ assertThat(expected2).isEqualTo(actual)
+ }
+
+ @Test
fun stackRounding() =
testScope.runTest {
val stackRounding by collectLastValue(underTest.shadeScrimRounding)
@@ -76,13 +88,17 @@ class NotificationStackAppearanceInteractorTest : SysuiTestCase() {
}
@Test(expected = IllegalStateException::class)
- fun setStackBounds_withImproperBounds_throwsException() =
+ fun stackNotificationScrimBounds_withImproperBounds_throwsException() =
testScope.runTest {
- underTest.setShadeScrimBounds(
- ShadeScrimBounds(
- top = 100f,
- bottom = 99f,
- )
+ underTest.setNotificationShadeScrimBounds(ShadeScrimBounds(top = 100f, bottom = 99f))
+ }
+
+ @Test(expected = IllegalStateException::class)
+ fun setQsPanelShape_withImproperBounds_throwsException() =
+ testScope.runTest {
+ val invalidBounds = ShadeScrimBounds(top = 0f, bottom = -10f)
+ underTest.setQsPanelShape(
+ ShadeScrimShape(bounds = invalidBounds, topRadius = 10, bottomRadius = 10)
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index 4944c8bd0221..14e7cdc50227 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -38,12 +38,14 @@ class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }
@Test
- fun onBoundsChanged() =
+ fun onScrimBoundsChanged() =
kosmos.testScope.runTest {
val bounds = ShadeScrimBounds(left = 5f, top = 15f, right = 25f, bottom = 35f)
underTest.onScrimBoundsChanged(bounds)
val stackBounds by
- collectLastValue(kosmos.notificationStackAppearanceInteractor.shadeScrimBounds)
+ collectLastValue(
+ kosmos.notificationStackAppearanceInteractor.notificationShadeScrimBounds
+ )
assertThat(stackBounds).isEqualTo(bounds)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 7802b921eae0..7c47264d448a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -36,8 +36,9 @@ import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.notification.modes.TestModeBuilder.MANUAL_DND
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.shared.settings.data.repository.secureSettingsRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository
@@ -45,18 +46,13 @@ import com.android.systemui.statusbar.policy.data.repository.fakeZenModeReposito
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import java.time.Duration
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
class ZenModeInteractorTest : SysuiTestCase() {
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val zenModeRepository = kosmos.fakeZenModeRepository
private val settingsRepository = kosmos.secureSettingsRepository
private val deviceProvisioningRepository = kosmos.fakeDeviceProvisioningRepository
@@ -65,166 +61,136 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun isZenAvailable_off() =
- testScope.runTest {
+ kosmos.runTest {
val isZenAvailable by collectLastValue(underTest.isZenAvailable)
deviceProvisioningRepository.setDeviceProvisioned(false)
- runCurrent()
-
assertThat(isZenAvailable).isFalse()
}
@Test
fun isZenAvailable_on() =
- testScope.runTest {
+ kosmos.runTest {
val isZenAvailable by collectLastValue(underTest.isZenAvailable)
deviceProvisioningRepository.setDeviceProvisioned(true)
- runCurrent()
-
assertThat(isZenAvailable).isTrue()
}
@Test
fun isZenModeEnabled_off() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
- runCurrent()
-
assertThat(enabled).isFalse()
}
@Test
fun isZenModeEnabled_alarms() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_ALARMS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun isZenModeEnabled_importantInterruptions() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun isZenModeEnabled_noInterruptions() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_NO_INTERRUPTIONS)
- runCurrent()
-
assertThat(enabled).isTrue()
}
@Test
fun testIsZenModeEnabled_unknown() =
- testScope.runTest {
+ kosmos.runTest {
val enabled by collectLastValue(underTest.isZenModeEnabled)
-
// this should fail if we ever add another zen mode type
zenModeRepository.updateZenMode(4)
- runCurrent()
-
assertThat(enabled).isFalse()
}
@Test
fun areNotificationsHiddenInShade_noPolicy() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(null)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOffShadeSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_OFF)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOnShadeNotSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_STATUS_BAR
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isFalse()
}
@Test
fun areNotificationsHiddenInShade_zenOnShadeSuppressed() =
- testScope.runTest {
+ kosmos.runTest {
val hidden by collectLastValue(underTest.areNotificationsHiddenInShade)
zenModeRepository.updateNotificationPolicy(
suppressedVisualEffects = Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST
)
zenModeRepository.updateZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS)
- runCurrent()
assertThat(hidden).isTrue()
}
@Test
fun shouldAskForZenDuration_falseForNonManualDnd() =
- testScope.runTest {
+ kosmos.runTest {
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(TestModeBuilder.EXAMPLE)).isFalse()
}
@Test
fun shouldAskForZenDuration_changesWithSetting() =
- testScope.runTest {
+ kosmos.runTest {
val manualDnd = TestModeBuilder().makeManualDnd().setActive(true).build()
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(manualDnd)).isFalse()
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_PROMPT)
- runCurrent()
-
assertThat(underTest.shouldAskForZenDuration(manualDnd)).isTrue()
}
@Test
fun activateMode_nonManualDnd() =
- testScope.runTest {
+ kosmos.runTest {
val mode = TestModeBuilder().setActive(false).build()
zenModeRepository.addModes(listOf(mode))
settingsRepository.setInt(ZEN_DURATION, 60)
- runCurrent()
underTest.activateMode(mode)
assertThat(zenModeRepository.getMode(mode.id)?.isActive).isTrue()
@@ -233,16 +199,14 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun activateMode_usesCorrectDuration() =
- testScope.runTest {
+ kosmos.runTest {
settingsRepository.setInt(ZEN_DURATION, ZEN_DURATION_FOREVER)
- runCurrent()
underTest.activateMode(MANUAL_DND)
assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id)).isNull()
zenModeRepository.deactivateMode(MANUAL_DND)
settingsRepository.setInt(ZEN_DURATION, 60)
- runCurrent()
underTest.activateMode(MANUAL_DND)
assertThat(zenModeRepository.getModeActiveDuration(MANUAL_DND.id))
@@ -251,7 +215,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun deactivateAllModes_updatesCorrectModes() =
- testScope.runTest {
+ kosmos.runTest {
zenModeRepository.activateMode(MANUAL_DND)
zenModeRepository.addModes(
listOf(
@@ -267,72 +231,69 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
fun activeModes_computesMainActiveMode() =
- testScope.runTest {
+ kosmos.runTest {
val activeModes by collectLastValue(underTest.activeModes)
zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
-
- runCurrent()
assertThat(activeModes?.modeNames).hasSize(0)
assertThat(activeModes?.mainMode).isNull()
zenModeRepository.activateMode("Other")
- runCurrent()
assertThat(activeModes?.modeNames).containsExactly("Mode Other")
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Other")
zenModeRepository.activateMode("Bedtime")
- runCurrent()
assertThat(activeModes?.modeNames)
.containsExactly("Mode Bedtime", "Mode Other")
.inOrder()
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
zenModeRepository.deactivateMode("Other")
- runCurrent()
assertThat(activeModes?.modeNames).containsExactly("Mode Bedtime")
assertThat(activeModes?.mainMode?.name).isEqualTo("Mode Bedtime")
zenModeRepository.deactivateMode("Bedtime")
- runCurrent()
assertThat(activeModes?.modeNames).hasSize(0)
assertThat(activeModes?.mainMode).isNull()
}
@Test
- fun getActiveModes_computesMainActiveMode() = runTest {
- zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
- zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
-
- var activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).hasSize(0)
- assertThat(activeModes.mainMode).isNull()
-
- zenModeRepository.activateMode("Other")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Other")
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
-
- zenModeRepository.activateMode("Bedtime")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Bedtime", "Mode Other").inOrder()
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
-
- zenModeRepository.deactivateMode("Other")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
- assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
-
- zenModeRepository.deactivateMode("Bedtime")
- activeModes = underTest.getActiveModes()
- assertThat(activeModes.modeNames).hasSize(0)
- assertThat(activeModes.mainMode).isNull()
- }
+ fun getActiveModes_computesMainActiveMode() =
+ kosmos.runTest {
+ zenModeRepository.addMode(id = "Bedtime", type = AutomaticZenRule.TYPE_BEDTIME)
+ zenModeRepository.addMode(id = "Other", type = AutomaticZenRule.TYPE_OTHER)
+
+ var activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+
+ zenModeRepository.activateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Other")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Other")
+
+ zenModeRepository.activateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames)
+ .containsExactly("Mode Bedtime", "Mode Other")
+ .inOrder()
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Other")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).containsExactly("Mode Bedtime")
+ assertThat(activeModes.mainMode?.name).isEqualTo("Mode Bedtime")
+
+ zenModeRepository.deactivateMode("Bedtime")
+ activeModes = underTest.getActiveModes()
+ assertThat(activeModes.modeNames).hasSize(0)
+ assertThat(activeModes.mainMode).isNull()
+ }
@Test
fun mainActiveMode_flows() =
- testScope.runTest {
+ kosmos.runTest {
val mainActiveMode by collectLastValue(underTest.mainActiveMode)
zenModeRepository.addModes(
@@ -355,51 +316,42 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
-
- runCurrent()
assertThat(mainActiveMode).isNull()
zenModeRepository.activateMode("Other")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Other")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_other)
zenModeRepository.activateMode("Bedtime")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Other")
- runCurrent()
assertThat(mainActiveMode?.name).isEqualTo("Mode Bedtime")
assertThat(mainActiveMode?.icon?.key?.resId)
.isEqualTo(R.drawable.ic_zen_mode_type_bedtime)
zenModeRepository.deactivateMode("Bedtime")
- runCurrent()
assertThat(mainActiveMode).isNull()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun dndMode_flows() =
- testScope.runTest {
+ kosmos.runTest {
val dndMode by collectLastValue(underTest.dndMode)
-
assertThat(dndMode!!.isActive).isFalse()
zenModeRepository.activateMode(MANUAL_DND)
- runCurrent()
-
assertThat(dndMode!!.isActive).isTrue()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
- testScope.runTest {
+ kosmos.runTest {
val blockingMedia by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC))
@@ -429,7 +381,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
assertThat(blockingMedia!!.modeNames)
@@ -440,7 +391,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
- testScope.runTest {
+ kosmos.runTest {
val blockingAlarms by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM))
@@ -470,7 +421,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
assertThat(blockingAlarms!!.modeNames)
@@ -481,7 +431,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() =
- testScope.runTest {
+ kosmos.runTest {
val blockingSystem by
collectLastValue(
underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM))
@@ -511,7 +461,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active")
assertThat(blockingSystem!!.modeNames)
@@ -522,7 +471,7 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(ModesEmptyShadeFix.FLAG_NAME, Flags.FLAG_MODES_UI, Flags.FLAG_MODES_API)
fun modesHidingNotifications_onlyIncludesModesWithNotifListSuppression() =
- testScope.runTest {
+ kosmos.runTest {
val modesHidingNotifications by collectLastValue(underTest.modesHidingNotifications)
zenModeRepository.addModes(
@@ -554,7 +503,6 @@ class ZenModeInteractorTest : SysuiTestCase() {
.build(),
)
)
- runCurrent()
assertThat(modesHidingNotifications?.map { it.name })
.containsExactly("Has list suppression 1", "Has list suppression 2")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
new file mode 100644
index 000000000000..4cbe33d66045
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialActionStateSaverTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.composable
+
+import androidx.compose.runtime.saveable.SaverScope
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.res.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialActionStateSaverTest : SysuiTestCase() {
+
+ private val saver = TutorialActionState.stateSaver()
+ private val saverScope: SaverScope =
+ object : SaverScope {
+ override fun canBeSaved(value: Any) = true
+ }
+
+ @Test
+ fun inProgressIsRestoredToNotStartedState() {
+ assertRestoredState(
+ savedState = InProgress(progress = 0f),
+ expectedRestoredState = NotStarted,
+ )
+ }
+
+ @Test
+ fun inProgressErrorIsRestoredToErrorState() {
+ assertRestoredState(
+ savedState = InProgressAfterError(InProgress(progress = 0f)),
+ expectedRestoredState = Error,
+ )
+ }
+
+ @Test
+ fun otherStatesAreRestoredToTheSameState() {
+ assertRestoredState(savedState = NotStarted, expectedRestoredState = NotStarted)
+ assertRestoredState(savedState = Error, expectedRestoredState = Error)
+ assertRestoredState(
+ savedState = Finished(successAnimation = R.raw.trackpad_home_success),
+ expectedRestoredState = Finished(successAnimation = R.raw.trackpad_home_success),
+ )
+ }
+
+ private fun assertRestoredState(
+ savedState: TutorialActionState,
+ expectedRestoredState: TutorialActionState,
+ ) {
+ val savedValue = with(saver) { saverScope.save(savedState) }
+ assertThat(saver.restore(savedValue!!)).isEqualTo(expectedRestoredState)
+ }
+}
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
new file mode 100644
index 000000000000..277df47438e3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/android16_patch_adaptive_background"/>
+ <foreground android:drawable="@drawable/android16_patch_adaptive_foreground"/>
+ <monochrome android:drawable="@drawable/android16_patch_monochrome"/>
+</adaptive-icon>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
new file mode 100644
index 000000000000..17c2b927f4fd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_background.xml
@@ -0,0 +1,245 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <group>
+ <clip-path
+ android:pathData="M0,0h108v108h-108z"/>
+ <path
+ android:pathData="M73,54L54,35L35,54L54,73L73,54Z"
+ android:fillColor="#34A853"/>
+ <path
+ android:pathData="M44.5,44.5L54,44.5L44.5,54L44.5,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,63.5L54,63.5L63.5,54L63.5,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,44.5L63.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,44.5L54,35L63.5,44.5L54,44.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,63.5L54,73L44.5,63.5L54,63.5Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M63.5,54L63.5,44.5L73,54L63.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M44.5,54L44.5,63.5L35,54L44.5,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M54,54L54,63.5L44.5,54L54,54Z"
+ android:fillColor="#1F8E3D"/>
+ <path
+ android:pathData="M82.5,25.5L82.5,35L73,25.5L82.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,44.5L63.5,35L73,44.5L63.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L82.5,35L73,44.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,35L92,35L82.5,44.5L82.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,35L54,35L63.5,25.5L63.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,44.5L82.5,44.5L73,54L73,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,25.5L63.5,25.5L73,16L73,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,35L63.5,35L73,25.5L73,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,63.5L82.5,73L73,63.5L82.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,82.5L63.5,73L73,82.5L63.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L82.5,73L73,82.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,73L92,73L82.5,82.5L82.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,73L54,73L63.5,63.5L63.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,82.5L82.5,82.5L73,92L73,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,63.5L63.5,63.5L73,54L73,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M73,73L63.5,73L73,63.5L73,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,63.5L44.5,73L35,63.5L44.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,82.5L25.5,73L35,82.5L25.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L44.5,73L35,82.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,73L54,73L44.5,82.5L44.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,73L16,73L25.5,63.5L25.5,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,82.5L44.5,82.5L35,92L35,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,63.5L25.5,63.5L35,54L35,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,73L25.5,73L35,63.5L35,73Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,25.5L44.5,35L35,25.5L44.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,44.5L25.5,35L35,44.5L25.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L44.5,35L35,44.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,35L54,35L44.5,44.5L44.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,35L16,35L25.5,25.5L25.5,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,44.5L44.5,44.5L35,54L35,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,25.5L25.5,25.5L35,16L35,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M35,35L25.5,35L35,25.5L35,35Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,25.5L54,25.5L63.5,16L63.5,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,6.5L54,6.5L44.5,16L44.5,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,25.5L44.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,25.5L54,35L44.5,25.5L54,25.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,6.5L54,-3L63.5,6.5L54,6.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,16L44.5,25.5L35,16L44.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,16L63.5,6.5L73,16L63.5,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,16L54,6.5L63.5,16L54,16Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,63.5L92,63.5L101.5,54L101.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,44.5L92,44.5L82.5,54L82.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,63.5L82.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,63.5L92,73L82.5,63.5L92,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,44.5L92,35L101.5,44.5L92,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M82.5,54L82.5,63.5L73,54L82.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M101.5,54L101.5,44.5L111,54L101.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M92,54L92,44.5L101.5,54L92,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,101.5L54,101.5L63.5,92L63.5,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,82.5L54,82.5L44.5,92L44.5,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,101.5L44.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,101.5L54,111L44.5,101.5L54,101.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,82.5L54,73L63.5,82.5L54,82.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M44.5,92L44.5,101.5L35,92L44.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M63.5,92L63.5,82.5L73,92L63.5,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M54,92L54,82.5L63.5,92L54,92Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,63.5L16,63.5L25.5,54L25.5,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,44.5L16,44.5L6.5,54L6.5,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,63.5L6.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,63.5L16,73L6.5,63.5L16,63.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,44.5L16,35L25.5,44.5L16,44.5Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M6.5,54L6.5,63.5L-3,54L6.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M25.5,54L25.5,44.5L35,54L25.5,54Z"
+ android:fillColor="#16161D"/>
+ <path
+ android:pathData="M16,54L16,44.5L25.5,54L16,54Z"
+ android:fillColor="#16161D"/>
+ </group>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
new file mode 100644
index 000000000000..4c2932399c1a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_adaptive_foreground.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#C6FF00"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.973 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#000000"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.557C62.817,58.796 63.205,58.796 63.444,58.557L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/android16_patch_monochrome.xml b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
new file mode 100644
index 000000000000..608d5ea6ee48
--- /dev/null
+++ b/packages/SystemUI/res/drawable/android16_patch_monochrome.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:strokeWidth="1"
+ android:pathData="M54.707,35.707L72.293,53.293A1,1 102.155,0 1,72.293 54.707L54.707,72.293A1,1 0,0 1,53.293 72.293L35.707,54.707A1,1 0,0 1,35.707 53.293L53.293,35.707A1,1 0,0 1,54.707 35.707z"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M55.237,35.177L72.823,52.763A1.75,1.75 67.835,0 1,72.823 55.237L55.237,72.823A1.75,1.75 77.684,0 1,52.763 72.823L35.177,55.237A1.75,1.75 0,0 1,35.177 52.763L52.763,35.177A1.75,1.75 0,0 1,55.237 35.177z"
+ android:strokeWidth="1.5"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M44.5,44.5h19v19h-19z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,44.5l9.5,9.5l-9.5,9.5l-9.5,-9.5z"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M54,35V73"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M73,54L35,54"
+ android:strokeWidth="0.75"
+ android:fillColor="#00000000"
+ android:strokeColor="#ffffff"/>
+ <path
+ android:pathData="M33.576,31.135l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,65.966l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M26.718,56l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M31.146,48l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M41.925,34.374l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M63.146,71l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M48.567,74.553l1.718,1.718l-1.718,1.718l-1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M51.146,26l1.146,1.146l-1.146,1.146l-1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M72.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M76.531,36.417l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M58.291,32.146l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M68.419,36.978l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M74.252,64.034l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M71.437,76.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M42.984,69.38l-1.146,1.146l-1.146,-1.146l1.146,-1.146z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M82.437,51.718l-1.718,1.718l-1.718,-1.718l1.718,-1.718z"
+ android:fillColor="#E8F5E9"/>
+ <path
+ android:pathData="M40.65,63.013C40.722,62.922 40.716,62.789 40.633,62.707V62.707C40.537,62.61 40.377,62.62 40.292,62.727C34.567,69.881 31.569,75.536 33.089,77.056C35.366,79.333 46.923,71.469 58.901,59.491C60.049,58.343 61.159,57.199 62.226,56.066C62.342,55.943 62.339,55.751 62.219,55.632L61.566,54.978C61.441,54.854 61.238,54.857 61.117,54.985C60.057,56.11 58.951,57.25 57.806,58.395C46.882,69.319 36.496,76.646 34.61,74.759C33.417,73.567 35.903,68.982 40.65,63.013Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M67.956,52.033C68.205,51.966 68.462,52.115 68.529,52.364C68.596,52.614 68.448,52.871 68.198,52.938L67.956,52.033ZM68.198,52.938L63.926,54.083L63.683,53.178L67.956,52.033L68.198,52.938Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M64.497,49.237C64.564,48.987 64.821,48.839 65.071,48.906C65.32,48.972 65.469,49.229 65.402,49.479L64.497,49.237ZM65.402,49.479L64.257,53.752L63.352,53.509L64.497,49.237L65.402,49.479Z"
+ android:fillColor="#ffffff"/>
+ <path
+ android:pathData="M66.145,51.236C64.869,49.961 62.83,49.931 61.591,51.17L58.825,53.937C58.585,54.176 58.585,54.564 58.825,54.803C59.063,55.042 59.452,55.042 59.691,54.803L60.436,54.057C60.915,53.579 61.69,53.579 62.169,54.057L63.324,55.212C63.802,55.691 63.802,56.466 63.324,56.945L62.578,57.69C62.339,57.929 62.339,58.318 62.578,58.556C62.817,58.796 63.205,58.796 63.444,58.556L66.211,55.79C67.45,54.551 67.42,52.512 66.145,51.236Z"
+ android:fillColor="#ffffff"/>
+</vector>
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 6eb7b730e105..c5f468e731f5 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,8 +14,8 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="0dp"
+ android:layout_width="@dimen/volume_dialog_slider_width"
+ android:layout_height="match_parent"
android:maxHeight="@dimen/volume_dialog_slider_height">
<com.google.android.material.slider.Slider
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
index 1c5da827eeb3..3d2ce4229bd5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/plugins/PluginEnabler.java
@@ -27,9 +27,10 @@ public interface PluginEnabler {
int DISABLED_INVALID_VERSION = 2;
int DISABLED_FROM_EXPLICIT_CRASH = 3;
int DISABLED_FROM_SYSTEM_CRASH = 4;
+ int DISABLED_UNKNOWN = 100;
@IntDef({ENABLED, DISABLED_MANUALLY, DISABLED_INVALID_VERSION, DISABLED_FROM_EXPLICIT_CRASH,
- DISABLED_FROM_SYSTEM_CRASH})
+ DISABLED_FROM_SYSTEM_CRASH, DISABLED_UNKNOWN})
@interface DisableReason {
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index ff788484c819..ec97b8a96c1f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -208,7 +208,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final InputMethodManager mInputMethodManager;
private final DelayableExecutor mMainExecutor;
private final Resources mResources;
- private final LiftToActivateListener mLiftToActivateListener;
private final TelephonyManager mTelephonyManager;
private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory;
private final FalsingCollector mFalsingCollector;
@@ -227,7 +226,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
LatencyTracker latencyTracker,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor,
- @Main Resources resources, LiftToActivateListener liftToActivateListener,
+ @Main Resources resources,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController.Factory emergencyButtonControllerFactory,
DevicePostureController devicePostureController,
@@ -244,7 +243,6 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;
mResources = resources;
- mLiftToActivateListener = liftToActivateListener;
mTelephonyManager = telephonyManager;
mEmergencyButtonControllerFactory = emergencyButtonControllerFactory;
mFalsingCollector = falsingCollector;
@@ -284,7 +282,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
+ emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
mUiEventLogger, mKeyguardKeyboardInteractor, mBouncerHapticPlayer,
mUserActivityNotifier);
@@ -292,14 +290,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+ mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
- mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
+ mTelephonyManager, mFalsingCollector,
emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
mKeyguardKeyboardInteractor, mBouncerHapticPlayer, mUserActivityNotifier
);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index e22736b69213..622b67f25da3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -297,13 +297,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
? mDisappearAnimationUtilsLocked
: mDisappearAnimationUtils;
- android.util.Log.i("KeyguardPINView", "startDisappearAnimation: " + finishRunnable);
disappearAnimationUtils.createAnimation(
this, 0, 200, mDisappearYTranslation, false,
mDisappearAnimationUtils.getInterpolator(), () -> {
if (finishRunnable != null) {
- android.util.Log.i("KeyguardPINView",
- "startDisappearAnimation, invoking run()");
finishRunnable.run();
}
},
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index d999994a3312..7f176de547bc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -34,7 +34,6 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
-import com.android.systemui.Flags;
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
@@ -44,7 +43,6 @@ import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
- private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
private final KeyguardKeyboardInteractor mKeyguardKeyboardInteractor;
protected PasswordTextView mPasswordEntry;
@@ -73,7 +71,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker,
- LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
@@ -85,7 +82,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor,
bouncerHapticPlayer, userActivityNotifier);
- mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mKeyguardKeyboardInteractor = keyguardKeyboardInteractor;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
@@ -151,10 +147,6 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
verifyPasswordAndUnlock();
}
});
-
- if (!Flags.simPinTalkbackFixForDoubleSubmit()) {
- okButton.setOnHoverListener(mLiftToActivateListener);
- }
}
if (pinInputFieldStyledFocusState()) {
collectFlow(mPasswordEntry, mKeyguardKeyboardInteractor.isAnyKeyboardConnected(),
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index b159a70066ce..9ae4cc6a4b4f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -56,7 +56,7 @@ public class KeyguardPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
DevicePostureController postureController, FeatureFlags featureFlags,
@@ -65,7 +65,7 @@ public class KeyguardPinViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 52c93f72206b..24f77d77dbe1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -91,7 +91,7 @@ public class KeyguardSimPinViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
@@ -99,7 +99,7 @@ public class KeyguardSimPinViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 9adc5bae1ada..e17e8cc05f7e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -89,7 +89,7 @@ public class KeyguardSimPukViewController
SecurityMode securityMode, LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
- LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
+ LatencyTracker latencyTracker,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
SelectedUserInteractor selectedUserInteractor,
@@ -97,7 +97,7 @@ public class KeyguardSimPukViewController
BouncerHapticPlayer bouncerHapticPlayer,
UserActivityNotifier userActivityNotifier) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
- messageAreaControllerFactory, latencyTracker, liftToActivateListener,
+ messageAreaControllerFactory, latencyTracker,
emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
keyguardKeyboardInteractor, bouncerHapticPlayer, userActivityNotifier);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
deleted file mode 100644
index 425e50ed6397..000000000000
--- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.keyguard;
-
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.accessibility.AccessibilityManager;
-
-import javax.inject.Inject;
-
-/**
- * Hover listener that implements lift-to-activate interaction for
- * accessibility. May be added to multiple views.
- */
-class LiftToActivateListener implements View.OnHoverListener {
- /** Manager used to query accessibility enabled state. */
- private final AccessibilityManager mAccessibilityManager;
-
- private boolean mCachedClickableState;
-
- @Inject
- LiftToActivateListener(AccessibilityManager accessibilityManager) {
- mAccessibilityManager = accessibilityManager;
- }
-
- @Override
- public boolean onHover(View v, MotionEvent event) {
- // When touch exploration is turned on, lifting a finger while
- // inside the view bounds should perform a click action.
- if (mAccessibilityManager.isEnabled()
- && mAccessibilityManager.isTouchExplorationEnabled()) {
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_HOVER_ENTER:
- // Lift-to-type temporarily disables double-tap
- // activation by setting the view as not clickable.
- mCachedClickableState = v.isClickable();
- v.setClickable(false);
- break;
- case MotionEvent.ACTION_HOVER_EXIT:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
- if ((x > v.getPaddingLeft()) && (y > v.getPaddingTop())
- && (x < v.getWidth() - v.getPaddingRight())
- && (y < v.getHeight() - v.getPaddingBottom())) {
- v.performClick();
- }
- v.setClickable(mCachedClickableState);
- break;
- }
- }
-
- // Pass the event to View.onHoverEvent() to handle accessibility.
- v.onHoverEvent(event);
-
- // Consume the event so it doesn't fall through to other views.
- return true;
- }
-} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index 5b433464c1c6..5cba464fc24c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -609,15 +609,12 @@ public class MagnificationImpl implements Magnification, CommandQueue.Callbacks
final WindowMagnificationController windowMagnificationController =
mWindowMagnificationControllerSupplier.get(displayId);
if (windowMagnificationController != null) {
- boolean isWindowMagnifierActivated = windowMagnificationController.isActivated();
- if (isWindowMagnifierActivated) {
- windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
- }
+ windowMagnificationController.updateDragHandleResourcesIfNeeded(shown);
if (shown) {
mA11yLogger.logWithPosition(
MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED,
- isWindowMagnifierActivated
+ windowMagnificationController.isActivated()
? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
: ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 08d3e17c03d7..1587ab16fc38 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1519,12 +1519,12 @@ class WindowMagnificationController implements View.OnTouchListener, SurfaceHold
}
void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) {
+ mSettingsPanelVisibility = settingsPanelIsShown;
+
if (!isActivated()) {
return;
}
- mSettingsPanelVisibility = settingsPanelIsShown;
-
mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown
? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset
: R.drawable.accessibility_window_magnification_drag_handle_background_inset));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index 68ec0f2d57ef..39f55803bb73 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -68,7 +68,7 @@ interface FingerprintPropertyRepository {
val sensorType: StateFlow<FingerprintSensorType>
/** The sensor location relative to each physical display. */
- val sensorLocations: Flow<Map<String, SensorLocationInternal>>
+ val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
}
@SysUISingleton
@@ -128,12 +128,14 @@ constructor(
initialValue = props.value.sensorType.toSensorType(),
)
- override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
- props.map {
- it.allLocations.associateBy { sensorLocationInternal ->
- sensorLocationInternal.displayId
- }
- }
+ override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
+ props
+ .map { props -> props.allLocations.associateBy { it.displayId } }
+ .stateIn(
+ applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = props.value.allLocations.associateBy { it.displayId },
+ )
override val propertiesInitialized: Flow<Boolean> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index d9ed9ca1f07b..ae855d19715e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -20,11 +20,11 @@ import android.content.Context
import android.graphics.Rect
import android.hardware.biometrics.SensorLocationInternal
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
-import com.android.systemui.biometrics.shared.model.SensorLocation
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shared.customization.data.SensorLocation
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
@@ -42,8 +42,8 @@ class FingerprintPropertyInteractor
constructor(
@Application private val applicationScope: CoroutineScope,
@Application private val context: Context,
- repository: FingerprintPropertyRepository,
- @Main configurationInteractor: ConfigurationInteractor,
+ private val repository: FingerprintPropertyRepository,
+ @Main private val configurationInteractor: ConfigurationInteractor,
displayStateInteractor: DisplayStateInteractor,
udfpsOverlayInteractor: UdfpsOverlayInteractor,
) {
@@ -61,11 +61,16 @@ constructor(
* Devices with multiple physical displays use unique display ids to determine which sensor is
* on the active physical display. This value represents a unique physical display id.
*/
- private val uniqueDisplayId: Flow<String> =
+ private val uniqueDisplayId: StateFlow<String> =
displayStateInteractor.displayChanges
- .map { context.display?.uniqueId }
+ .map { context.display.uniqueId }
.filterNotNull()
.distinctUntilChanged()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Eagerly,
+ initialValue = EMPTY_DISPLAY_ID,
+ )
/**
* Sensor location for the:
@@ -73,13 +78,15 @@ constructor(
* - device's natural screen resolution
* - device's natural orientation
*/
- private val unscaledSensorLocation: Flow<SensorLocationInternal> =
- combine(repository.sensorLocations, uniqueDisplayId) { locations, displayId ->
+ private val unscaledSensorLocation: StateFlow<SensorLocationInternal> =
+ combineStates(repository.sensorLocations, uniqueDisplayId, applicationScope) {
+ locations,
+ displayId ->
// Devices without multiple physical displays do not use the display id as the key;
// instead, the key is an empty string.
locations.getOrDefault(
displayId,
- locations.getOrDefault("", SensorLocationInternal.DEFAULT),
+ locations.getOrDefault(EMPTY_DISPLAY_ID, SensorLocationInternal.DEFAULT),
)
}
@@ -89,18 +96,18 @@ constructor(
* - current screen resolution
* - device's natural orientation
*/
- val sensorLocation: Flow<SensorLocation> =
- combine(unscaledSensorLocation, configurationInteractor.scaleForResolution) {
+ val sensorLocation: StateFlow<SensorLocation> =
+ combineStates(
unscaledSensorLocation,
- scale ->
- val sensorLocation =
- SensorLocation(
- naturalCenterX = unscaledSensorLocation.sensorLocationX,
- naturalCenterY = unscaledSensorLocation.sensorLocationY,
- naturalRadius = unscaledSensorLocation.sensorRadius,
- scale = scale,
- )
- sensorLocation
+ configurationInteractor.scaleForResolution,
+ applicationScope,
+ ) { unscaledSensorLocation, scale ->
+ SensorLocation(
+ naturalCenterX = unscaledSensorLocation.sensorLocationX,
+ naturalCenterY = unscaledSensorLocation.sensorLocationY,
+ naturalRadius = unscaledSensorLocation.sensorRadius,
+ scale = scale,
+ )
}
/**
@@ -111,4 +118,19 @@ constructor(
*/
val udfpsSensorBounds: Flow<Rect> =
udfpsOverlayInteractor.udfpsOverlayParams.map { it.sensorBounds }.distinctUntilChanged()
+
+ companion object {
+
+ private const val EMPTY_DISPLAY_ID = ""
+
+ /** Combine two state flows to another state flow. */
+ private fun <T1, T2, R> combineStates(
+ flow1: StateFlow<T1>,
+ flow2: StateFlow<T2>,
+ scope: CoroutineScope,
+ transform: (T1, T2) -> R,
+ ): StateFlow<R> =
+ combine(flow1, flow2) { v1, v2 -> transform(v1, v2) }
+ .stateIn(scope, SharingStarted.Eagerly, transform(flow1.value, flow2.value))
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index 22b2888a51a9..a44778657d25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.bouncer.data.repository
+import android.annotation.SuppressLint
import android.os.Build
import android.util.Log
import com.android.keyguard.KeyguardSecurityModel
@@ -51,7 +52,11 @@ interface KeyguardBouncerRepository {
val primaryBouncerShow: StateFlow<Boolean>
val primaryBouncerShowingSoon: StateFlow<Boolean>
val primaryBouncerStartingToHide: StateFlow<Boolean>
- val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+ val primaryBouncerStartingDisappearAnimation: MutableSharedFlow<Runnable?>
+
+ fun isPrimaryBouncerStartingDisappearAnimation(): Boolean
+
+ fun isDebuggable(): Boolean
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
val primaryBouncerScrimmed: StateFlow<Boolean>
@@ -128,7 +133,7 @@ interface KeyguardBouncerRepository {
}
@SysUISingleton
-class KeyguardBouncerRepositoryImpl
+open class KeyguardBouncerRepositoryImpl
@Inject
constructor(
private val clock: SystemClock,
@@ -144,9 +149,19 @@ constructor(
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
- private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
+
+ @SuppressLint("SharedFlowCreation")
override val primaryBouncerStartingDisappearAnimation =
- _primaryBouncerDisappearAnimation.asStateFlow()
+ MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+ override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+ val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+ return if (!replayCache.isEmpty()) {
+ replayCache.last() != null
+ } else {
+ false
+ }
+ }
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
@@ -177,6 +192,7 @@ constructor(
_keyguardAuthenticatedPrimaryAuth.asSharedFlow()
/** Whether the user requested to show the bouncer when device is already authenticated */
+ @SuppressLint("SharedFlowCreation")
private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
_userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
@@ -226,7 +242,7 @@ constructor(
}
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
- _primaryBouncerDisappearAnimation.value = runnable
+ primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
}
override fun setPanelExpansion(panelExpansion: Float) {
@@ -265,9 +281,11 @@ constructor(
_lastShownSecurityMode.value = securityMode
}
+ override fun isDebuggable() = Build.IS_DEBUGGABLE
+
/** Sets up logs for state flows. */
private fun setUpLogging() {
- if (!Build.IS_DEBUGGABLE) {
+ if (!isDebuggable()) {
return
}
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 641400a50c89..0c6d7920d7f3 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
@@ -22,6 +22,7 @@ import android.os.Handler
import android.os.Trace
import android.util.Log
import android.view.View
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
@@ -54,7 +55,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Encapsulates business logic for interacting with the lock-screen primary (pin/pattern/password)
@@ -145,7 +145,7 @@ constructor(
TAG,
"PrimaryBouncerInteractor#show is being called before the " +
"primaryBouncerDelegate is set. Let's exit early so we don't " +
- "set the wrong primaryBouncer state."
+ "set the wrong primaryBouncer state.",
)
return false
}
@@ -197,7 +197,7 @@ constructor(
if (isFullyShowing()) {
SysUiStatsLog.write(
SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
- SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
+ SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN,
)
dismissCallbackRegistry.notifyDismissCancelled()
}
@@ -223,7 +223,7 @@ constructor(
fun setPanelExpansion(expansion: Float) {
val oldExpansion = repository.panelExpansionAmount.value
val expansionChanged = oldExpansion != expansion
- if (repository.primaryBouncerStartingDisappearAnimation.value == null) {
+ if (!repository.isPrimaryBouncerStartingDisappearAnimation()) {
repository.setPanelExpansion(expansion)
}
@@ -272,7 +272,7 @@ constructor(
*/
fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
- cancelAction: Runnable?
+ cancelAction: Runnable?,
) {
repository.bouncerDismissActionModel =
if (onDismissAction != null && cancelAction != null) {
@@ -344,7 +344,7 @@ constructor(
fun isFullyShowing(): Boolean {
return (repository.primaryBouncerShowingSoon.value || isBouncerShowing()) &&
repository.panelExpansionAmount.value == KeyguardBouncerConstants.EXPANSION_VISIBLE &&
- repository.primaryBouncerStartingDisappearAnimation.value == null
+ !repository.isPrimaryBouncerStartingDisappearAnimation()
}
/** Returns whether bouncer is scrimmed. */
@@ -361,7 +361,7 @@ constructor(
/** Return whether bouncer is animating away. */
fun isAnimatingAway(): Boolean {
- return repository.primaryBouncerStartingDisappearAnimation.value != null
+ return repository.isPrimaryBouncerStartingDisappearAnimation()
}
/** Return whether bouncer will dismiss with actions */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index d5c815d649c4..434a9ce58c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -170,8 +170,6 @@ object KeyguardBouncerViewBinder {
launch {
viewModel.startDisappearAnimation.collect {
- android.util.Log.i("KeyguardBouncerViewBinder",
- "viewModel.startDisappearAnimation: $it")
securityContainerController.startDisappearAnimation(it)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index 5baec1e025e8..73aaf7f8e460 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -29,11 +29,11 @@ import android.view.KeyEvent.isConfirmKey
import android.view.View
import androidx.compose.ui.input.key.KeyEvent
import androidx.compose.ui.input.key.KeyEventType
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.PinShapeAdapter
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer
import com.android.systemui.res.R
import dagger.assisted.Assisted
@@ -50,7 +50,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Holds UI state and handles user input for the PIN code bouncer UI. */
class PinBouncerViewModel
@@ -289,11 +288,10 @@ constructor(
* feedback on the view.
*/
fun onDigitButtonDown(view: View?) {
- if (ComposeBouncerFlags.isOnlyComposeBouncerEnabled()) {
- // Current PIN bouncer informs FalsingInteractor#avoidGesture() upon every Pin button
- // touch.
- super.onDown()
- }
+ // This ends up calling FalsingInteractor#avoidGesture() each time a PIN button is touched.
+ // It helps make sure that legitimate touch in the PIN bouncer isn't treated as false touch.
+ super.onDown()
+
if (bouncerHapticPlayer?.isEnabled == true) {
bouncerHapticPlayer.playNumpadKeyFeedback()
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
index b79538aac3e6..60cdccdeaadd 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCoreStartable.kt
@@ -21,8 +21,9 @@ import javax.inject.Inject
/** Initializes classes related to falsing. */
@SysUISingleton
-class FalsingCoreStartable @Inject constructor(val falsingCollector: FalsingCollector) :
- CoreStartable {
+class FalsingCoreStartable
+@Inject
+constructor(@FalsingCollectorActual val falsingCollector: FalsingCollector) : CoreStartable {
override fun start() {
falsingCollector.init()
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 747a2a9bd887..7fcdd9596049 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -52,7 +52,7 @@ interface ConfigurationRepository {
/** Called whenever the configuration has changed. */
val onConfigurationChange: Flow<Unit>
- val scaleForResolution: Flow<Float>
+ val scaleForResolution: StateFlow<Float>
val configurationValues: Flow<Configuration>
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index 97a23e1a5010..4d39b033cd7b 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -23,6 +23,7 @@ import android.view.Surface
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -60,7 +61,7 @@ interface ConfigurationInteractor {
val configurationValues: Flow<Configuration>
/** Emits the current resolution scaling factor */
- val scaleForResolution: Flow<Float>
+ val scaleForResolution: StateFlow<Float>
/** Given [resourceId], emit the dimension pixel size on config change */
fun dimensionPixelSize(resourceId: Int): Flow<Int>
@@ -121,5 +122,5 @@ class ConfigurationInteractorImpl(private val repository: ConfigurationRepositor
override val configurationValues: Flow<Configuration> = repository.configurationValues
- override val scaleForResolution: Flow<Float> = repository.scaleForResolution
+ override val scaleForResolution: StateFlow<Float> = repository.scaleForResolution
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
index 6c6d730819f3..911327a0bd18 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryUdfpsInteractor.kt
@@ -17,10 +17,10 @@
package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
-import com.android.systemui.biometrics.shared.model.SensorLocation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.shared.customization.data.SensorLocation
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
diff --git a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
index 9af259a9b642..d10958a2ce4a 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ContextualEducationMetricsLogger.kt
@@ -42,8 +42,8 @@ class ContextualEducationMetricsLogger @Inject constructor() {
}
SysUiStatsLog.write(
SysUiStatsLog.CONTEXTUAL_EDUCATION_TRIGGERED,
- statsGestureType,
statsEducationType,
+ statsGestureType,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
index 950a727aedae..0ab5a80e0044 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
@@ -25,6 +25,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
@@ -45,7 +46,10 @@ import com.android.systemui.res.R
fun ActionKeyTutorialScreen(onDoneButtonClicked: () -> Unit, onBack: () -> Unit) {
BackHandler(onBack = onBack)
val screenConfig = buildScreenConfig()
- var actionState: TutorialActionState by remember { mutableStateOf(NotStarted) }
+ var actionState: TutorialActionState by
+ rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+ mutableStateOf(NotStarted)
+ }
val focusRequester = remember { FocusRequester() }
Box(
modifier =
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
index 08e0a9d52faa..21afa40c441b 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
@@ -38,6 +38,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
@@ -69,6 +71,31 @@ sealed interface TutorialActionState {
data class InProgressAfterError(val inProgress: InProgress) :
TutorialActionState, Progress by inProgress
+
+ companion object {
+ fun stateSaver(): Saver<TutorialActionState, Any> {
+ val classKey = "class"
+ val successAnimationKey = "animation"
+ return mapSaver(
+ save = {
+ buildMap {
+ put(classKey, it::class.java.name)
+ if (it is Finished) put(successAnimationKey, it.successAnimation)
+ }
+ },
+ restore = { map ->
+ when (map[classKey] as? String) {
+ NotStarted::class.java.name,
+ InProgress::class.java.name -> NotStarted
+ Error::class.java.name,
+ InProgressAfterError::class.java.name -> Error
+ Finished::class.java.name -> Finished(map[successAnimationKey]!! as Int)
+ else -> NotStarted
+ }
+ },
+ )
+ }
+ }
}
interface Progress {
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
index b0816ce608a0..dc0d7b689d0a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialAnimation.kt
@@ -131,10 +131,14 @@ private fun SuccessAnimation(
) {
val composition by
rememberLottieComposition(LottieCompositionSpec.RawRes(finishedState.successAnimation))
+ var animationFinished by rememberSaveable(key = "animationFinished") { mutableStateOf(false) }
val progress by animateLottieCompositionAsState(composition, iterations = 1)
+ if (progress == 1f) {
+ animationFinished = true
+ }
LottieAnimation(
composition = composition,
- progress = { progress },
+ progress = { if (animationFinished) 1f else progress },
dynamicProperties = animationProperties,
modifier = Modifier.fillMaxSize(),
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
index 7a72732ea6bf..18cabad6b2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt
@@ -33,6 +33,7 @@ import android.util.Log
import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking
import com.android.systemui.SystemUIAppComponentFactoryBase
import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback
+import com.android.systemui.biometrics.domain.interactor.FingerprintPropertyInteractor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager
@@ -46,6 +47,7 @@ class CustomizationProvider :
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
@Inject lateinit var shadeModeInteractor: ShadeModeInteractor
+ @Inject lateinit var fingerprintPropertyInteractor: FingerprintPropertyInteractor
@Inject lateinit var previewManager: KeyguardRemotePreviewManager
@Inject @Main lateinit var mainDispatcher: CoroutineDispatcher
@@ -345,6 +347,14 @@ class CustomizationProvider :
}
private fun queryRuntimeValues(): Cursor {
+ // If not UDFPS, the udfpsLocation will be null
+ val udfpsLocation =
+ if (fingerprintPropertyInteractor.isUdfps.value) {
+ fingerprintPropertyInteractor.sensorLocation.value
+ } else {
+ null
+ }
+
return MatrixCursor(
arrayOf(
Contract.RuntimeValuesTable.Columns.NAME,
@@ -358,6 +368,9 @@ class CustomizationProvider :
if (shadeModeInteractor.isShadeLayoutWide.value) 1 else 0,
)
)
+ addRow(
+ arrayOf(Contract.RuntimeValuesTable.KEY_UDFPS_LOCATION, udfpsLocation?.encode())
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 647362873015..5baef915ea01 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3492,7 +3492,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public void showSurfaceBehindKeyguard() {
mSurfaceBehindRemoteAnimationRequested = true;
- if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS) {
+ if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
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 f5e0c81ca9a2..b1a2ec92401a 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
@@ -20,7 +20,6 @@ import android.animation.FloatEvaluator
import android.animation.IntEvaluator
import com.android.keyguard.KeyguardViewController
import com.android.systemui.accessibility.domain.interactor.AccessibilityInteractor
-import com.android.systemui.biometrics.shared.model.SensorLocation
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -34,6 +33,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shared.customization.data.SensorLocation
import com.android.systemui.util.kotlin.sample
import dagger.Lazy
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 8fe225a8b93f..88fdc83fa7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -100,7 +100,7 @@ constructor(
keyguardClockInteractor.clockShouldBeCentered.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = false,
+ initialValue = true,
)
// To translate elements below smartspace in weather clock to avoid overlapping between date
diff --git a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
index 40f59744e038..7f1c7a525f97 100644
--- a/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/plugins/PluginEnablerImpl.java
@@ -18,6 +18,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.shared.plugins.PluginEnabler;
@@ -28,6 +29,7 @@ import javax.inject.Singleton;
/** */
@Singleton
public class PluginEnablerImpl implements PluginEnabler {
+ private static final String TAG = "PluginEnablerImpl";
private static final String CRASH_DISABLED_PLUGINS_PREF_FILE = "auto_disabled_plugins_prefs";
private final PackageManager mPm;
@@ -64,8 +66,13 @@ public class PluginEnablerImpl implements PluginEnabler {
@Override
public boolean isEnabled(ComponentName component) {
- return mPm.getComponentEnabledSetting(component)
- != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ try {
+ return mPm.getComponentEnabledSetting(component)
+ != PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ } catch (IllegalArgumentException ex) {
+ Log.e(TAG, "Package Manager Exception", ex);
+ return false;
+ }
}
@Override
@@ -73,6 +80,6 @@ public class PluginEnablerImpl implements PluginEnabler {
if (isEnabled(componentName)) {
return ENABLED;
}
- return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_MANUALLY);
+ return mAutoDisabledPrefs.getInt(componentName.flattenToString(), DISABLED_UNKNOWN);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
index c0c0aea073f1..465d08b36ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModel.kt
@@ -17,13 +17,14 @@
package com.android.systemui.qs.ui.viewmodel
import androidx.compose.runtime.getValue
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
@@ -44,6 +45,7 @@ class QuickSettingsShadeOverlayContentViewModel
constructor(
val shadeInteractor: ShadeInteractor,
val sceneInteractor: SceneInteractor,
+ val notificationStackAppearanceInteractor: NotificationStackAppearanceInteractor,
val shadeHeaderViewModelFactory: ShadeHeaderViewModel.Factory,
quickSettingsContainerViewModelFactory: QuickSettingsContainerViewModel.Factory,
) : ExclusiveActivatable() {
@@ -92,6 +94,11 @@ constructor(
awaitCancellation()
}
+ /** Notifies that the bounds of the QuickSettings panel have changed. */
+ fun onPanelShapeChanged(shape: ShadeScrimShape?) {
+ notificationStackAppearanceInteractor.setQsPanelShape(shape)
+ }
+
fun onScrimClicked() {
shadeInteractor.collapseQuickSettingsShade(loggingReason = "shade scrim clicked")
}
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 3a23a71cf7bf..8657c1723507 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
@@ -94,7 +94,6 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy
-import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
@@ -717,14 +716,7 @@ constructor(
}
applicationScope.launch {
- keyguardInteractor.isAodAvailable
- .flatMapLatest { isAodAvailable ->
- if (!isAodAvailable) {
- powerInteractor.detailedWakefulness
- } else {
- emptyFlow()
- }
- }
+ powerInteractor.detailedWakefulness
.distinctUntilChangedBy { it.isAwake() }
.collect { wakefulness ->
when {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
index 5b8277284117..89454b84a528 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -1,5 +1,7 @@
justinweir@google.com
syeonlee@google.com
+nicomazz@google.com
+burakov@google.com
per-file *Notification* = set noparent
per-file *Notification* = file:../statusbar/notification/OWNERS
@@ -9,13 +11,13 @@ per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google
per-file *ShadeHeader* = syeonlee@google.com, kozynski@google.com, asc@google.com
per-file *Interactor* = set noparent
-per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Interactor* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com
per-file *Repository* = set noparent
-per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com
+per-file *Repository* = justinweir@google.com, syeonlee@google.com, nijamkin@google.com, nicomazz@google.com, burakov@google.com
-per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
+per-file NotificationShadeWindow* = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
-per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
-per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com, syeonlee@google.com, nicomazz@google.com, burakov@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
index e358dcec8b10..ec9bba7c1f0f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayChangeLatencyTracker.kt
@@ -95,7 +95,7 @@ constructor(
*/
@Synchronized
fun onShadeDisplayChanging(displayId: Int) {
- previousJob?.cancel(CancellationException("New shade move in progress"))
+ previousJob?.cancel(CancellationException("New shade move in progress to $displayId"))
previousJob = bgScope.launch { onShadeDisplayChangingAsync(displayId) }
}
@@ -109,8 +109,8 @@ constructor(
val reason =
when (e) {
is CancellationException ->
- "Shade move cancelled as a new move is being done " +
- "before the previous one finished."
+ "Shade move to $displayId cancelled as a new move is being done " +
+ "before the previous one finished. Message: ${e.message}"
else -> "Shade move cancelled."
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
index 17b5e5b584b4..d53f9f7ec595 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/ShadeDisplayPolicy.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.display
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoSet
@@ -33,11 +34,33 @@ interface ShadeDisplayPolicy {
val displayId: StateFlow<Int>
}
+/** Return the latest element the user intended to expand in the shade (notifications or QS). */
+interface ShadeExpansionIntent {
+ /**
+ * Returns the latest element the user intended to expand in the shade (notifications or QS).
+ *
+ * When the shade moves to a different display (e.g., due to a touch on the status bar of an
+ * external display), it's first collapsed and then re-expanded on the target display.
+ *
+ * If the user was trying to open a specific element (QS or notifications) when the shade was on
+ * the original display, that intention might be lost during the collapse/re-expand transition.
+ * This is used to preserve the user's intention, ensuring the correct element is expanded on
+ * the target display.
+ *
+ * Note that the expansion intent is kept for a very short amount of time (ideally, just a bit
+ * above the time it takes for the shade to collapse)
+ */
+ fun consumeExpansionIntent(): ShadeElement?
+}
+
@Module
interface ShadeDisplayPolicyModule {
@Binds fun provideDefaultPolicy(impl: StatusBarTouchShadeDisplayPolicy): ShadeDisplayPolicy
+ @Binds
+ fun provideShadeExpansionIntent(impl: StatusBarTouchShadeDisplayPolicy): ShadeExpansionIntent
+
@IntoSet
@Binds
fun provideDefaultDisplayPolicyToSet(impl: DefaultDisplayShadePolicy): ShadeDisplayPolicy
diff --git a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
index 30b086f03d66..91020aa7bdb0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/display/StatusBarTouchShadeDisplayPolicy.kt
@@ -18,16 +18,25 @@ package com.android.systemui.shade.display
import android.util.Log
import android.view.Display
+import android.view.MotionEvent
import com.android.app.tracing.coroutines.launchTraced
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.shade.ShadeOnDefaultDisplayWhenLocked
+import com.android.systemui.shade.domain.interactor.NotificationShadeElement
+import com.android.systemui.shade.domain.interactor.QSShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor.ShadeElement
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -49,14 +58,20 @@ class StatusBarTouchShadeDisplayPolicy
constructor(
displayRepository: DisplayRepository,
keyguardRepository: KeyguardRepository,
- @Background val backgroundScope: CoroutineScope,
- @ShadeOnDefaultDisplayWhenLocked val shadeOnDefaultDisplayWhenLocked: Boolean,
-) : ShadeDisplayPolicy {
+ @Background private val backgroundScope: CoroutineScope,
+ @ShadeOnDefaultDisplayWhenLocked private val shadeOnDefaultDisplayWhenLocked: Boolean,
+ private val shadeInteractor: Lazy<ShadeInteractor>,
+ private val qsShadeElement: Lazy<QSShadeElement>,
+ private val notificationElement: Lazy<NotificationShadeElement>,
+) : ShadeDisplayPolicy, ShadeExpansionIntent {
override val name: String = "status_bar_latest_touch"
private val currentDisplayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
private val availableDisplayIds: StateFlow<Set<Int>> = displayRepository.displayIds
+ private var latestIntent = AtomicReference<ShadeElement?>()
+ private var timeoutJob: Job? = null
+
override val displayId: StateFlow<Int> =
if (shadeOnDefaultDisplayWhenLocked) {
keyguardRepository.isKeyguardShowing
@@ -75,8 +90,29 @@ constructor(
private var removalListener: Job? = null
/** Called when the status bar on the given display is touched. */
- fun onStatusBarTouched(statusBarDisplayId: Int) {
+ fun onStatusBarTouched(event: MotionEvent, statusBarWidth: Int) {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ updateShadeDisplayIfNeeded(event)
+ updateExpansionIntent(event, statusBarWidth)
+ }
+
+ override fun consumeExpansionIntent(): ShadeElement? {
+ return latestIntent.getAndSet(null)
+ }
+
+ private fun updateExpansionIntent(event: MotionEvent, statusBarWidth: Int) {
+ val element = classifyStatusBarEvent(event, statusBarWidth)
+ latestIntent.set(element)
+ timeoutJob?.cancel()
+ timeoutJob =
+ backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#intentTimeout") {
+ delay(EXPANSION_INTENT_EXPIRY)
+ latestIntent.set(null)
+ }
+ }
+
+ private fun updateShadeDisplayIfNeeded(event: MotionEvent) {
+ val statusBarDisplayId = event.displayId
if (statusBarDisplayId !in availableDisplayIds.value) {
Log.e(TAG, "Got touch on unknown display $statusBarDisplayId")
return
@@ -90,6 +126,17 @@ constructor(
}
}
+ private fun classifyStatusBarEvent(
+ motionEvent: MotionEvent,
+ statusbarWidth: Int,
+ ): ShadeElement {
+ val xPercentage = motionEvent.x / statusbarWidth
+ val threshold = shadeInteractor.get().getTopEdgeSplitFraction()
+ return if (xPercentage < threshold) {
+ notificationElement.get()
+ } else qsShadeElement.get()
+ }
+
private fun monitorDisplayRemovals(): Job {
return backgroundScope.launchTraced("StatusBarTouchDisplayPolicy#monitorDisplayRemovals") {
currentDisplayId.subscriptionCount
@@ -112,5 +159,6 @@ constructor(
private companion object {
const val TAG = "StatusBarTouchDisplayPolicy"
+ val EXPANSION_INTENT_EXPIRY = 2.seconds
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
index 691a383cb338..f67d33122063 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractor.kt
@@ -30,6 +30,7 @@ import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
import com.android.systemui.shade.ShadeTraceLogger.logMoveShadeWindowTo
import com.android.systemui.shade.ShadeTraceLogger.traceReparenting
import com.android.systemui.shade.data.repository.ShadeDisplaysRepository
+import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.window.flags.Flags
import java.util.Optional
@@ -49,6 +50,7 @@ constructor(
@Main private val mainThreadContext: CoroutineContext,
private val shadeDisplayChangeLatencyTracker: ShadeDisplayChangeLatencyTracker,
shadeExpandedInteractor: Optional<ShadeExpandedStateInteractor>,
+ private val shadeExpansionIntent: ShadeExpansionIntent,
) : CoreStartable {
private val shadeExpandedInteractor =
@@ -90,10 +92,7 @@ constructor(
withContext(mainThreadContext) {
traceReparenting {
shadeDisplayChangeLatencyTracker.onShadeDisplayChanging(destinationId)
- val expandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
- expandedElement?.collapse(reason = "Shade window move")
- reparentToDisplayId(id = destinationId)
- expandedElement?.expand(reason = "Shade window move")
+ collapseAndExpandShadeIfNeeded { reparentToDisplayId(id = destinationId) }
checkContextDisplayMatchesExpected(destinationId)
}
}
@@ -106,6 +105,18 @@ constructor(
}
}
+ private suspend fun collapseAndExpandShadeIfNeeded(wrapped: () -> Unit) {
+ val previouslyExpandedElement = shadeExpandedInteractor.currentlyExpandedElement.value
+ previouslyExpandedElement?.collapse(reason = COLLAPSE_EXPAND_REASON)
+
+ wrapped()
+
+ // If the user was trying to expand a specific shade element, let's make sure to expand
+ // that one. Otherwise, we can just re-expand the previous expanded element.
+ shadeExpansionIntent.consumeExpansionIntent()?.expand(COLLAPSE_EXPAND_REASON)
+ ?: previouslyExpandedElement?.expand(reason = COLLAPSE_EXPAND_REASON)
+ }
+
private fun checkContextDisplayMatchesExpected(destinationId: Int) {
if (shadeContext.displayId != destinationId) {
Log.wtf(
@@ -125,5 +136,6 @@ constructor(
private companion object {
const val TAG = "ShadeDisplaysInteractor"
+ const val COLLAPSE_EXPAND_REASON = "Shade window move"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
index dd3abeec5a72..aba5a6bceb10 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeExpandedStateInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.shade.domain.interactor
+import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -24,6 +25,8 @@ import com.android.systemui.shade.domain.interactor.ShadeExpandedStateInteractor
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.util.kotlin.Utils.Companion.combineState
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,7 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
-import kotlinx.coroutines.withTimeout
+import kotlinx.coroutines.withTimeoutOrNull
/**
* Wrapper around [ShadeInteractor] to facilitate expansion and collapse of Notifications and quick
@@ -47,7 +50,7 @@ interface ShadeExpandedStateInteractor {
val currentlyExpandedElement: StateFlow<ShadeElement?>
/** An element from the shade window that can be expanded or collapsed. */
- abstract class ShadeElement {
+ sealed class ShadeElement {
/** Expands the shade element, returning when the expansion is done */
abstract suspend fun expand(reason: String)
@@ -56,17 +59,18 @@ interface ShadeExpandedStateInteractor {
}
}
+private val EXPAND_COLLAPSE_TIMEOUT: Duration = 1.seconds
+
@SysUISingleton
class ShadeExpandedStateInteractorImpl
@Inject
constructor(
- private val shadeInteractor: ShadeInteractor,
+ shadeInteractor: ShadeInteractor,
@Background private val bgScope: CoroutineScope,
+ private val notificationElement: NotificationShadeElement,
+ private val qsElement: QSShadeElement,
) : ShadeExpandedStateInteractor {
- private val notificationElement = NotificationElement()
- private val qsElement = QSElement()
-
override val currentlyExpandedElement: StateFlow<ShadeElement?> =
if (SceneContainerFlag.isEnabled) {
combineState(
@@ -84,35 +88,54 @@ constructor(
} else {
MutableStateFlow(null)
}
+}
- inner class NotificationElement : ShadeElement() {
- override suspend fun expand(reason: String) {
- shadeInteractor.expandNotificationsShade(reason)
- shadeInteractor.shadeExpansion.waitUntil(1f)
+private suspend fun StateFlow<Float>.waitUntil(f: Float, coroutineContext: CoroutineContext) {
+ // it's important to not do this in the main thread otherwise it will block any rendering.
+ withContext(coroutineContext) {
+ withTimeoutOrNull(EXPAND_COLLAPSE_TIMEOUT) {
+ traceWaitForExpansion(expansion = f) { first { it == f } }
}
+ ?: Log.e(
+ "ShadeExpStateInteractor",
+ "Timed out after ${EXPAND_COLLAPSE_TIMEOUT.inWholeMilliseconds}ms while waiting " +
+ "for expansion to match $f. Current one: $value",
+ )
+ }
+}
- override suspend fun collapse(reason: String) {
- shadeInteractor.collapseNotificationsShade(reason)
- shadeInteractor.shadeExpansion.waitUntil(0f)
- }
+@SysUISingleton
+class NotificationShadeElement
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+ override suspend fun expand(reason: String) {
+ shadeInteractor.expandNotificationsShade(reason)
+ shadeInteractor.shadeExpansion.waitUntil(1f, bgContext)
}
- inner class QSElement : ShadeElement() {
- override suspend fun expand(reason: String) {
- shadeInteractor.expandQuickSettingsShade(reason)
- shadeInteractor.qsExpansion.waitUntil(1f)
- }
+ override suspend fun collapse(reason: String) {
+ shadeInteractor.collapseNotificationsShade(reason)
+ shadeInteractor.shadeExpansion.waitUntil(0f, bgContext)
+ }
+}
- override suspend fun collapse(reason: String) {
- shadeInteractor.collapseQuickSettingsShade(reason)
- shadeInteractor.qsExpansion.waitUntil(0f)
- }
+@SysUISingleton
+class QSShadeElement
+@Inject
+constructor(
+ private val shadeInteractor: ShadeInteractor,
+ @Background private val bgContext: CoroutineContext,
+) : ShadeElement() {
+ override suspend fun expand(reason: String) {
+ shadeInteractor.expandQuickSettingsShade(reason)
+ shadeInteractor.qsExpansion.waitUntil(1f, bgContext)
}
- private suspend fun StateFlow<Float>.waitUntil(f: Float) {
- // it's important to not do this in the main thread otherwise it will block any rendering.
- withContext(bgScope.coroutineContext) {
- withTimeout(1.seconds) { traceWaitForExpansion(expansion = f) { first { it == f } } }
- }
+ override suspend fun collapse(reason: String) {
+ shadeInteractor.collapseQuickSettingsShade(reason)
+ shadeInteractor.qsExpansion.waitUntil(0f, bgContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
index 5ef5a7d2139c..af51c49204bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java
@@ -284,6 +284,7 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca
| WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
| WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW;
lp.setTitle("ImmersiveModeConfirmation");
+ lp.accessibilityTitle = mSysUiContext.getString(R.string.immersive_cling_title);
lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
lp.token = getWindowToken();
return lp;
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 ea48fb4ffd92..086c32cbae5d 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
@@ -60,7 +60,6 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio
import com.android.systemui.statusbar.notification.data.NotificationDataLayerModule;
import com.android.systemui.statusbar.notification.domain.NotificationDomainLayerModule;
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor;
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.icon.ConversationIconManager;
import com.android.systemui.statusbar.notification.icon.IconManager;
@@ -109,7 +108,6 @@ import javax.inject.Provider;
* Dagger Module for classes found within the com.android.systemui.statusbar.notification package.
*/
@Module(includes = {
- FooterViewModelModule.class,
KeyguardNotificationVisibilityProviderModule.class,
NotificationDataLayerModule.class,
NotificationDomainLayerModule.class,
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 a670f69df601..c88dd7af6b24 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
@@ -40,6 +40,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
@@ -333,11 +334,9 @@ public class FooterView extends StackScrollerDecorView {
/**
* Whether the touch is outside the Clear all button.
- *
- * TODO(b/293167744): This is an artifact from the time when we could press underneath the
- * shade to dismiss it. Check if it's safe to remove.
*/
public boolean isOnEmptySpace(float touchX, float touchY) {
+ SceneContainerFlag.assertInLegacyMode();
return touchX < mContent.getX()
|| touchX > mContent.getX() + mContent.getWidth()
|| touchY < mContent.getY()
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 5696e9f0c5a2..c895c41960d4 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
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel
import android.content.Intent
import android.provider.Settings
import com.android.internal.jank.InteractionJankMonitor
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor
@@ -32,10 +31,8 @@ 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 dagger.Module
-import dagger.Provides
-import java.util.Optional
-import javax.inject.Provider
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -44,7 +41,9 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** ViewModel for [FooterView]. */
-class FooterViewModel(
+class FooterViewModel
+@AssistedInject
+constructor(
activeNotificationsInteractor: ActiveNotificationsInteractor,
notificationSettingsInteractor: NotificationSettingsInteractor,
seenNotificationsInteractor: SeenNotificationsInteractor,
@@ -141,25 +140,9 @@ class FooterViewModel(
AnimatedValue.NotAnimating(!messageVisible)
},
)
-}
-// TODO: b/293167744 - remove this, use new viewmodel style
-@Module
-object FooterViewModelModule {
- @Provides
- @SysUISingleton
- fun provideOptional(
- activeNotificationsInteractor: Provider<ActiveNotificationsInteractor>,
- notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
- seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
- shadeInteractor: Provider<ShadeInteractor>,
- ): Optional<FooterViewModel> =
- Optional.of(
- FooterViewModel(
- activeNotificationsInteractor.get(),
- notificationSettingsInteractor.get(),
- seenNotificationsInteractor.get(),
- shadeInteractor.get(),
- )
- )
+ @AssistedFactory
+ interface Factory {
+ fun create(): FooterViewModel
+ }
}
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 70e27a981b49..7b3a4710b69c 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
@@ -504,6 +504,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
CharSequence redactedMessage = systemUiContext.getString(
R.string.redacted_notification_single_line_text
);
+ redacted.setWhen(original.getWhen());
if (originalStyle instanceof MessagingStyle oldStyle) {
MessagingStyle newStyle = new MessagingStyle(oldStyle.getUser());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
index 98d704c75d33..f3ee34f45a50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row.icon
import android.annotation.WorkerThread
import android.app.ActivityManager
import android.app.Flags
+import android.app.Flags.notificationsRedesignThemedAppIcons
import android.content.Context
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Color
@@ -29,6 +30,8 @@ import android.util.Log
import com.android.internal.R
import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.mono.MonoIconThemeController
import com.android.launcher3.util.UserIconInfo
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
@@ -55,6 +58,7 @@ interface AppIconProvider {
packageName: String,
context: Context,
withWorkProfileBadge: Boolean = false,
+ themed: Boolean = true,
): Drawable
/**
@@ -74,6 +78,17 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
dumpManager.registerNormalDumpable(TAG, this)
}
+ private class NotificationIcons(context: Context?, fillResIconDpi: Int, iconBitmapSize: Int) :
+ BaseIconFactory(context, fillResIconDpi, iconBitmapSize) {
+
+ init {
+ if (notificationsRedesignThemedAppIcons()) {
+ // Initialize the controller so that we can support themed icons.
+ mThemeController = MonoIconThemeController()
+ }
+ }
+ }
+
private val iconFactory: BaseIconFactory
get() {
val isLowRam = ActivityManager.isLowRamDeviceStatic()
@@ -83,7 +98,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
if (isLowRam) R.dimen.notification_small_icon_size_low_ram
else R.dimen.notification_small_icon_size
)
- return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize)
+ return NotificationIcons(sysuiContext, res.configuration.densityDpi, iconSize)
}
private val cache = NotifCollectionCache<Drawable>()
@@ -92,12 +107,15 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
// Add a suffix to distinguish the app installed on the work profile, since the icon will
// be different.
val key = packageName + if (withWorkProfileBadge) WORK_SUFFIX else ""
- return cache.getOrFetch(key) { fetchAppIcon(packageName, context, withWorkProfileBadge) }
+ return cache.getOrFetch(key) {
+ fetchAppIcon(packageName, context, withWorkProfileBadge, themed)
+ }
}
@WorkerThread
@@ -105,6 +123,7 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
val pm = context.packageManager
val icon = pm.getApplicationInfo(packageName, 0).loadUnbadgedIcon(pm)
@@ -113,13 +132,14 @@ constructor(@ShadeDisplayAware private val sysuiContext: Context, dumpManager: D
IconOptions().apply {
setUser(userIconInfo(context, withWorkProfileBadge))
setBitmapGenerationMode(BaseIconFactory.MODE_HARDWARE)
- // This color is not used since we're not showing the themed icons. We're just
- // setting it so that the icon factory doesn't try to extract colors from our bitmap
- // (since it won't work, given it's a hardware bitmap).
+ // This color will not be used, but we're just setting it so that the icon factory
+ // doesn't try to extract colors from our bitmap (since it won't work, given it's a
+ // hardware bitmap).
setExtractedColor(Color.BLUE)
}
val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
- return badgedIcon.newIcon(sysuiContext)
+ val creationFlags = if (themed) BitmapInfo.FLAG_THEMED else 0
+ return badgedIcon.newIcon(sysuiContext, creationFlags)
}
private fun userIconInfo(context: Context, withWorkProfileBadge: Boolean): UserIconInfo {
@@ -165,6 +185,7 @@ class NoOpIconProvider : AppIconProvider {
packageName: String,
context: Context,
withWorkProfileBadge: Boolean,
+ themed: Boolean,
): Drawable {
Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.")
return ColorDrawable(Color.WHITE)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
index 64fdf6fc2708..bb4aa86fc6a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row.icon
+import android.app.Flags.notificationsRedesignThemedAppIcons
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
@@ -74,6 +75,7 @@ constructor(
sbn.packageName,
context,
withWorkProfileBadge,
+ themed = notificationsRedesignThemedAppIcons(),
)
}
}
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 7b55e83a0a99..a8a1318664f2 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
@@ -512,23 +512,27 @@ public class NotificationStackScrollLayout
*/
private final Path mRoundedClipPath = new Path();
+ /** The clip path defining where we are NOT allowed to draw. */
+ private final Path mNegativeRoundedClipPath = new Path();
+
/**
* The clip Path used to clip the launching notification. This may be different
* from the normal path, as the views launch animation could start clipped.
*/
private final Path mLaunchedNotificationClipPath = new Path();
- /**
- * Should we use rounded rect clipping right now
- */
+ /** Should we use rounded rect clipping right now */
private boolean mShouldUseRoundedRectClipping = false;
+ /** Should we set an out path for the drawing canvas */
+ private boolean mShouldUseNegativeRoundedRectClipping = false;
+
private int mRoundedRectClippingLeft;
private int mRoundedRectClippingTop;
private int mRoundedRectClippingBottom;
private int mRoundedRectClippingRight;
private int mRoundedRectClippingYTranslation;
- private final float[] mBgCornerRadii = new float[8];
+ private final float[] mRoundedClipCornerRadii = new float[8];
/**
* Whether stackY should be animated in case the view is getting shorter than the scroll
@@ -3864,7 +3868,7 @@ public class NotificationStackScrollLayout
if (!SceneContainerFlag.isEnabled()) {
return !isInsideQsHeader(ev);
}
- ShadeScrimShape shape = mScrollViewFields.getScrimClippingShape();
+ ShadeScrimShape shape = mScrollViewFields.getClippingShape();
if (shape == null) {
return true; // When there is no scrim, consider this event scrollable.
}
@@ -5390,7 +5394,8 @@ public class NotificationStackScrollLayout
println(pw, "pulsing", mPulsing);
println(pw, "expanded", mIsExpanded);
println(pw, "headsUpPinned", mInHeadsUpPinnedMode);
- println(pw, "qsClipping", mShouldUseRoundedRectClipping);
+ println(pw, "roundedRectClipping", mShouldUseRoundedRectClipping);
+ println(pw, "negativeRoundedRectClipping", mShouldUseNegativeRoundedRectClipping);
println(pw, "qsClipDismiss", mDismissUsingRowTranslationX);
println(pw, "visibility", visibilityString(getVisibility()));
println(pw, "alpha", getAlpha());
@@ -5469,8 +5474,8 @@ public class NotificationStackScrollLayout
pw.append(" r=").print(mRoundedRectClippingRight);
pw.append(" b=").print(mRoundedRectClippingBottom);
pw.append(" +y=").print(mRoundedRectClippingYTranslation);
- pw.append("} topRadius=").print(mBgCornerRadii[0]);
- pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
+ pw.append("} topRadius=").print(mRoundedClipCornerRadii[0]);
+ pw.append(" bottomRadius=").println(mRoundedClipCornerRadii[4]);
}
public boolean isFullyHidden() {
@@ -5917,14 +5922,12 @@ public class NotificationStackScrollLayout
mScrollListener = listener;
}
- /**
- * Set rounded rect clipping bounds on this view.
- */
+ /** Sets rounded rect where the view is allowed to draw. */
@Override
- public void setScrimClippingShape(@Nullable ShadeScrimShape shape) {
+ public void setClippingShape(@Nullable ShadeScrimShape shape) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
- if (Objects.equals(mScrollViewFields.getScrimClippingShape(), shape)) return;
- mScrollViewFields.setScrimClippingShape(shape);
+ if (Objects.equals(mScrollViewFields.getClippingShape(), shape)) return;
+ mScrollViewFields.setClippingShape(shape);
mShouldUseRoundedRectClipping = shape != null;
mRoundedClipPath.reset();
if (shape != null) {
@@ -5933,17 +5936,36 @@ public class NotificationStackScrollLayout
mRoundedRectClippingTop = (int) bounds.getTop();
mRoundedRectClippingRight = (int) bounds.getRight();
mRoundedRectClippingBottom = (int) bounds.getBottom();
- mBgCornerRadii[0] = shape.getTopRadius();
- mBgCornerRadii[1] = shape.getTopRadius();
- mBgCornerRadii[2] = shape.getTopRadius();
- mBgCornerRadii[3] = shape.getTopRadius();
- mBgCornerRadii[4] = shape.getBottomRadius();
- mBgCornerRadii[5] = shape.getBottomRadius();
- mBgCornerRadii[6] = shape.getBottomRadius();
- mBgCornerRadii[7] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[0] = shape.getTopRadius();
+ mRoundedClipCornerRadii[1] = shape.getTopRadius();
+ mRoundedClipCornerRadii[2] = shape.getTopRadius();
+ mRoundedClipCornerRadii[3] = shape.getTopRadius();
+ mRoundedClipCornerRadii[4] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[5] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[6] = shape.getBottomRadius();
+ mRoundedClipCornerRadii[7] = shape.getBottomRadius();
mRoundedClipPath.addRoundRect(
bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
- mBgCornerRadii, Path.Direction.CW);
+ mRoundedClipCornerRadii, Path.Direction.CW);
+ }
+ invalidate();
+ }
+
+ @Override
+ public void setNegativeClippingShape(@Nullable ShadeScrimShape shape) {
+ if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
+ if (Objects.equals(mScrollViewFields.getNegativeClippingShape(), shape)) return;
+
+ mScrollViewFields.setNegativeClippingShape(shape);
+ mShouldUseNegativeRoundedRectClipping = shape != null;
+ mNegativeRoundedClipPath.reset();
+ if (shape != null) {
+ ShadeScrimBounds bounds = shape.getBounds();
+ float bottomRadius = shape.getBottomRadius();
+ mNegativeRoundedClipPath.addRoundRect(
+ bounds.getLeft(), bounds.getTop(), bounds.getRight(), bounds.getBottom(),
+ new float[]{0, 0, 0, 0, bottomRadius, bottomRadius, bottomRadius, bottomRadius},
+ Path.Direction.CW);
}
invalidate();
}
@@ -5956,21 +5978,22 @@ public class NotificationStackScrollLayout
SceneContainerFlag.assertInLegacyMode();
if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
&& mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
- && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
+ && mRoundedClipCornerRadii[0] == topRadius
+ && mRoundedClipCornerRadii[5] == bottomRadius) {
return;
}
mRoundedRectClippingLeft = left;
mRoundedRectClippingTop = top;
mRoundedRectClippingBottom = bottom;
mRoundedRectClippingRight = right;
- mBgCornerRadii[0] = topRadius;
- mBgCornerRadii[1] = topRadius;
- mBgCornerRadii[2] = topRadius;
- mBgCornerRadii[3] = topRadius;
- mBgCornerRadii[4] = bottomRadius;
- mBgCornerRadii[5] = bottomRadius;
- mBgCornerRadii[6] = bottomRadius;
- mBgCornerRadii[7] = bottomRadius;
+ mRoundedClipCornerRadii[0] = topRadius;
+ mRoundedClipCornerRadii[1] = topRadius;
+ mRoundedClipCornerRadii[2] = topRadius;
+ mRoundedClipCornerRadii[3] = topRadius;
+ mRoundedClipCornerRadii[4] = bottomRadius;
+ mRoundedClipCornerRadii[5] = bottomRadius;
+ mRoundedClipCornerRadii[6] = bottomRadius;
+ mRoundedClipCornerRadii[7] = bottomRadius;
updateRoundedClipPath();
}
@@ -5992,7 +6015,7 @@ public class NotificationStackScrollLayout
mRoundedRectClippingTop + mRoundedRectClippingYTranslation,
mRoundedRectClippingRight,
mRoundedRectClippingBottom + mRoundedRectClippingYTranslation,
- mBgCornerRadii, Path.Direction.CW);
+ mRoundedClipCornerRadii, Path.Direction.CW);
if (mShouldUseRoundedRectClipping) {
invalidate();
}
@@ -6116,35 +6139,49 @@ public class NotificationStackScrollLayout
}
@Override
- protected void dispatchDraw(Canvas canvas) {
- if (mShouldUseRoundedRectClipping && !mLaunchingNotification) {
+ protected void dispatchDraw(@NonNull Canvas canvas) {
+ if (!mLaunchingNotification) {
// When launching notifications, we're clipping the children individually instead of in
// dispatchDraw
- // Let's clip rounded.
- canvas.clipPath(mRoundedClipPath);
+ if (mShouldUseRoundedRectClipping) {
+ // Let's clip rounded.
+ canvas.clipPath(mRoundedClipPath);
+ }
+ if (mShouldUseNegativeRoundedRectClipping) {
+ // subtract the negative path if it is defined
+ canvas.clipOutPath(mNegativeRoundedClipPath);
+ }
}
super.dispatchDraw(canvas);
}
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- if (mShouldUseRoundedRectClipping && mLaunchingNotification) {
+ boolean shouldUseClipping =
+ mShouldUseRoundedRectClipping || mShouldUseNegativeRoundedRectClipping;
+ if (mLaunchingNotification && shouldUseClipping) {
// Let's clip children individually during notification launch
canvas.save();
ExpandableView expandableView = (ExpandableView) child;
Path clipPath;
+ Path clipOutPath;
if (expandableView.isExpandAnimationRunning()
|| ((ExpandableView) child).hasExpandingChild()) {
// When launching the notification, it is not clipped by this layout, but by the
// view itself. This is because the view is Translating in Z, where this clipPath
// wouldn't apply.
clipPath = null;
+ clipOutPath = null;
} else {
clipPath = mRoundedClipPath;
+ clipOutPath = mNegativeRoundedClipPath;
}
- if (clipPath != null) {
+ if (mShouldUseRoundedRectClipping && clipPath != null) {
canvas.clipPath(clipPath);
}
+ if (mShouldUseNegativeRoundedRectClipping && clipOutPath != null) {
+ canvas.clipOutPath(clipOutPath);
+ }
boolean result = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return result;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index c717e3b229be..dc0fae80d041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1207,7 +1207,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getEmptyShadeViewHeight();
}
- /** Set the max alpha for keyguard */
+ /**
+ * Controls fading out Notifications during animations over the LockScreen, such opening or
+ * closing the shade. Note that we don't restrict Notification alpha in certain cases,
+ * like when the Shade is opened from a HUN.
+ */
public void setMaxAlphaForKeyguard(float alpha, String source) {
mMaxAlphaForKeyguard = alpha;
mMaxAlphaForKeyguardSource = source;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
index fa20e43d0534..2593ef07751b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ScrollViewFields.kt
@@ -33,7 +33,10 @@ import java.util.function.Consumer
*/
class ScrollViewFields {
/** Used to produce the clipping path */
- var scrimClippingShape: ShadeScrimShape? = null
+ var clippingShape: ShadeScrimShape? = null
+
+ /** Used to produce a negative clipping path */
+ var negativeClippingShape: ShadeScrimShape? = null
/** Scroll state of the notification shade. */
var scrollState: ShadeScrollState = ShadeScrollState()
@@ -97,7 +100,8 @@ class ScrollViewFields {
fun dump(pw: IndentingPrintWriter) {
pw.printSection("StackViewStates") {
- pw.println("scrimClippingShape", scrimClippingShape)
+ pw.println("scrimClippingShape", clippingShape)
+ pw.println("negativeClippingShape", negativeClippingShape)
pw.println("scrollState", scrollState)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index 5ec4c8988697..1d196c2fc079 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.stack.data.repository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
import java.util.function.Consumer
import javax.inject.Inject
@@ -42,7 +43,15 @@ class NotificationPlaceholderRepository @Inject constructor() {
*
* When `null`, clipping should not be applied to notifications.
*/
- val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+ val notificationShadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
+
+ /**
+ * The shape of the QuickSettings overlay panel. Used to clip Notification content when the QS
+ * covers it.
+ *
+ * When `null`, it doesn't affect notification clipping.
+ */
+ val qsPanelShape = MutableStateFlow<ShadeScrimShape?>(null)
/** height made available to the notifications in the size-constrained mode of lock screen. */
val constrainedAvailableSpace = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index d4dd1d4b9e0b..406a0dbb120f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -26,6 +26,7 @@ import com.android.systemui.statusbar.notification.stack.data.repository.Notific
import com.android.systemui.statusbar.notification.stack.shared.model.AccessibilityScrollEvent
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimShape
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrollState
import java.util.function.Consumer
import javax.inject.Inject
@@ -47,8 +48,11 @@ constructor(
shadeInteractor: ShadeInteractor,
) {
/** The bounds of the notification stack in the current scene. */
- val shadeScrimBounds: StateFlow<ShadeScrimBounds?> =
- placeholderRepository.shadeScrimBounds.asStateFlow()
+ val notificationShadeScrimBounds: StateFlow<ShadeScrimBounds?> =
+ placeholderRepository.notificationShadeScrimBounds.asStateFlow()
+
+ /** The shape of the QuickSettingsShadeOverlay panel */
+ val qsPanelShape: StateFlow<ShadeScrimShape?> = placeholderRepository.qsPanelShape.asStateFlow()
/**
* Whether the stack is expanding from GONE-with-HUN to SHADE
@@ -119,9 +123,15 @@ constructor(
}
/** Sets the position of the notification stack in the current scene. */
- fun setShadeScrimBounds(bounds: ShadeScrimBounds?) {
- check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
- placeholderRepository.shadeScrimBounds.value = bounds
+ fun setNotificationShadeScrimBounds(bounds: ShadeScrimBounds?) {
+ checkValidBounds(bounds)
+ placeholderRepository.notificationShadeScrimBounds.value = bounds
+ }
+
+ /** Sets the bounds of the QuickSettings overlay panel */
+ fun setQsPanelShape(shape: ShadeScrimShape?) {
+ checkValidBounds(shape?.bounds)
+ placeholderRepository.qsPanelShape.value = shape
}
/** Updates the current scroll state of the notification shade. */
@@ -156,4 +166,8 @@ constructor(
fun setConstrainedAvailableSpace(height: Int) {
placeholderRepository.constrainedAvailableSpace.value = height
}
+
+ private fun checkValidBounds(bounds: ShadeScrimBounds?) {
+ check(bounds == null || bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
index d302fb67dddb..5fec0965f6a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/view/NotificationScrollView.kt
@@ -49,8 +49,14 @@ interface NotificationScrollView {
/** Max alpha for this view */
fun setMaxAlpha(alpha: Float)
- /** Set the clipping bounds used when drawing */
- fun setScrimClippingShape(shape: ShadeScrimShape?)
+ /** Sets a clipping shape, which defines the drawable area of this view. */
+ fun setClippingShape(shape: ShadeScrimShape?)
+
+ /**
+ * Sets a clipping shape, which defines the non-drawable area of this view. The final drawing
+ * area is the difference of the clipping shape, and the negative clipping shape.
+ */
+ fun setNegativeClippingShape(shape: ShadeScrimShape?)
/** set the y position in px of the top of the stack in this view's coordinates */
fun setStackTop(stackTop: Float)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index 1d7e658932ac..6385d53dbc8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -99,6 +99,9 @@ constructor(
.inflate(R.layout.status_bar_notification_shelf, view, false) as NotificationShelf
view.setShelf(shelf)
+ // Create viewModels once, and only when needed.
+ val footerViewModel by lazy { viewModel.footerViewModelFactory.create() }
+ val emptyShadeViewModel by lazy { viewModel.emptyShadeViewModelFactory.create() }
view.repeatWhenAttached {
lifecycleScope.launch {
if (SceneContainerFlag.isEnabled) {
@@ -109,12 +112,18 @@ constructor(
val hasNonClearableSilentNotifications: StateFlow<Boolean> =
viewModel.hasNonClearableSilentNotifications.stateIn(this)
- launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
+ launch {
+ reinflateAndBindFooter(
+ footerViewModel,
+ view,
+ hasNonClearableSilentNotifications,
+ )
+ }
launch {
if (ModesEmptyShadeFix.isEnabled) {
- reinflateAndBindEmptyShade(view)
+ reinflateAndBindEmptyShade(emptyShadeViewModel, view)
} else {
- bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
+ bindEmptyShadeLegacy(emptyShadeViewModel, view)
}
}
launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) }
@@ -134,31 +143,30 @@ constructor(
}
private suspend fun reinflateAndBindFooter(
+ footerViewModel: FooterViewModel,
parentView: NotificationStackScrollLayout,
hasNonClearableSilentNotifications: StateFlow<Boolean>,
) {
- viewModel.footer.getOrNull()?.let { footerViewModel ->
- // The footer needs to be re-inflated every time the theme or the font size changes.
- configuration
- .inflateLayout<FooterView>(
- if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer
- else R.layout.status_bar_notification_footer,
- parentView,
- attachToRoot = false,
- )
- .flowOn(backgroundDispatcher)
- .collectLatest { footerView: FooterView ->
- traceAsync("bind FooterView") {
- parentView.setFooterView(footerView)
- bindFooter(
- footerView,
- footerViewModel,
- parentView,
- hasNonClearableSilentNotifications,
- )
- }
+ // The footer needs to be re-inflated every time the theme or the font size changes.
+ configuration
+ .inflateLayout<FooterView>(
+ if (NotifRedesignFooter.isEnabled) R.layout.notification_2025_footer
+ else R.layout.status_bar_notification_footer,
+ parentView,
+ attachToRoot = false,
+ )
+ .flowOn(backgroundDispatcher)
+ .collectLatest { footerView: FooterView ->
+ traceAsync("bind FooterView") {
+ parentView.setFooterView(footerView)
+ bindFooter(
+ footerView,
+ footerViewModel,
+ parentView,
+ hasNonClearableSilentNotifications,
+ )
}
- }
+ }
}
/**
@@ -219,7 +227,10 @@ constructor(
notificationActivityStarter.get().startHistoryIntent(view, /* showHistory= */ true)
}
- private suspend fun reinflateAndBindEmptyShade(parentView: NotificationStackScrollLayout) {
+ private suspend fun reinflateAndBindEmptyShade(
+ emptyShadeViewModel: EmptyShadeViewModel,
+ parentView: NotificationStackScrollLayout,
+ ) {
ModesEmptyShadeFix.assertInNewMode()
// The empty shade needs to be re-inflated every time the theme or the font size
// changes.
@@ -233,7 +244,7 @@ constructor(
.collectLatest { emptyShadeView: EmptyShadeView ->
traceAsync("bind EmptyShadeView") {
parentView.setEmptyShadeView(emptyShadeView)
- bindEmptyShade(emptyShadeView, viewModel.emptyShadeViewFactory.create())
+ bindEmptyShade(emptyShadeView, emptyShadeViewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
index ef68b4de5291..8709d27bddcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt
@@ -79,8 +79,17 @@ constructor(
launch {
viewModel
- .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset)
- .collectTraced { view.setScrimClippingShape(it) }
+ .notificationScrimShape(
+ cornerRadius = scrimRadius,
+ viewLeftOffset = viewLeftOffset,
+ )
+ .collectTraced { view.setClippingShape(it) }
+ }
+
+ launch {
+ viewModel.qsScrimShape(viewLeftOffset = viewLeftOffset).collectTraced {
+ view.setNegativeClippingShape(it)
+ }
}
launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index fcc671a5bae6..5ed1889de01e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -56,8 +56,8 @@ class NotificationListViewModel
constructor(
val shelf: NotificationShelfViewModel,
val hideListViewModel: HideListViewModel,
- val footer: Optional<FooterViewModel>,
- val emptyShadeViewFactory: EmptyShadeViewModel.Factory,
+ val footerViewModelFactory: FooterViewModel.Factory,
+ val emptyShadeViewModelFactory: EmptyShadeViewModel.Factory,
val logger: Optional<NotificationLoggerViewModel>,
activeNotificationsInteractor: ActiveNotificationsInteractor,
notificationStackInteractor: NotificationStackInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index 1bb205cbcb61..80ebf43baf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -59,7 +59,7 @@ class NotificationScrollViewModel
@AssistedInject
constructor(
dumpManager: DumpManager,
- stackAppearanceInteractor: NotificationStackAppearanceInteractor,
+ private val stackAppearanceInteractor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
private val remoteInputInteractor: RemoteInputInteractor,
private val sceneInteractor: SceneInteractor,
@@ -221,7 +221,7 @@ constructor(
private val shadeScrimClipping: Flow<ShadeScrimClipping?> =
combine(
qsAllowsClipping,
- stackAppearanceInteractor.shadeScrimBounds,
+ stackAppearanceInteractor.notificationShadeScrimBounds,
stackAppearanceInteractor.shadeScrimRounding,
) { qsAllowsClipping, bounds, rounding ->
bounds?.takeIf { qsAllowsClipping }?.let { ShadeScrimClipping(it, rounding) }
@@ -229,7 +229,7 @@ constructor(
.distinctUntilChanged()
.dumpWhileCollecting("stackClipping")
- fun shadeScrimShape(
+ fun notificationScrimShape(
cornerRadius: Flow<Int>,
viewLeftOffset: Flow<Int>,
): Flow<ShadeScrimShape?> =
@@ -243,6 +243,12 @@ constructor(
}
.dumpWhileCollecting("shadeScrimShape")
+ fun qsScrimShape(viewLeftOffset: Flow<Int>): Flow<ShadeScrimShape?> =
+ combine(stackAppearanceInteractor.qsPanelShape, viewLeftOffset) { shape, leftOffset ->
+ shape?.let { it.copy(bounds = it.bounds.minus(leftOffset = leftOffset)) }
+ }
+ .dumpWhileCollecting("qsScrimShape")
+
/**
* Max alpha to apply directly to the view based on the compose placeholder.
*
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 49cd7cb4fb8d..5d550226a79e 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
@@ -40,7 +41,6 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -89,7 +89,7 @@ constructor(
/** Notifies that the bounds of the notification scrim have changed. */
fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
- interactor.setShadeScrimBounds(bounds)
+ interactor.setNotificationShadeScrimBounds(bounds)
}
/** Sets the available space */
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 aa1308931f99..3f44f7bdef90 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -234,7 +234,7 @@ private constructor(
)
}
if (ShadeWindowGoesAround.isEnabled && event.action == MotionEvent.ACTION_DOWN) {
- lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(context.displayId)
+ lazyStatusBarShadeDisplayPolicy.get().onStatusBarTouched(event, mView.width)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 284e23e5a288..47c82e309d9b 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -26,7 +26,10 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
@@ -48,8 +51,13 @@ fun GestureTutorialScreen(
onBack: () -> Unit,
) {
BackHandler(onBack = onBack)
+ var cachedTutorialState: TutorialActionState by
+ rememberSaveable(stateSaver = TutorialActionState.stateSaver()) {
+ mutableStateOf(NotStarted)
+ }
val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
- val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
+ val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(cachedTutorialState)
+ cachedTutorialState = tutorialState
TouchpadGesturesHandlingBox(
motionEventConsumer,
tutorialState,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 71fe22ba4b01..9cf02f26c9f7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -82,10 +82,6 @@ constructor(
ringerMode: RingerMode?,
): Int {
val isStreamOffline = level == 0 || isMuted
- when (ringerMode?.value) {
- AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
- AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
- }
if (isRoutedToBluetooth) {
return if (stream == AudioManager.STREAM_VOICE_CALL) {
R.drawable.ic_volume_bt_sco
@@ -98,29 +94,39 @@ constructor(
}
}
+ val isLevelLow = level < (levelMax + levelMin) / 2
return if (isStreamOffline) {
+ val ringerOfflineIcon =
+ when (ringerMode?.value) {
+ AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
+ AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
+ else -> null
+ }
when (stream) {
AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
- AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_NOTIFICATION ->
+ ringerOfflineIcon ?: R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_RING -> ringerOfflineIcon ?: R.drawable.ic_volume_ringer_vibrate
AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
else -> null
- } ?: getIconForStream(stream)
- } else {
- if (level < (levelMax + levelMin) / 2) {
- // This icon is different on TV
- R.drawable.ic_volume_media_low
- } else {
- getIconForStream(stream)
}
- }
+ } else {
+ null
+ } ?: getIconForStream(stream = stream, isLevelLow = isLevelLow)
}
@DrawableRes
- private fun getIconForStream(stream: Int): Int {
+ private fun getIconForStream(stream: Int, isLevelLow: Boolean): Int {
return when (stream) {
AudioManager.STREAM_ACCESSIBILITY -> R.drawable.ic_volume_accessibility
- AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media
+ AudioManager.STREAM_MUSIC ->
+ if (isLevelLow) {
+ // This icon is different on TV
+ R.drawable.ic_volume_media_low
+ } else {
+ R.drawable.ic_volume_media
+ }
AudioManager.STREAM_RING -> R.drawable.ic_ring_volume
AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer
AudioManager.STREAM_ALARM -> R.drawable.ic_alarm
@@ -135,7 +141,9 @@ constructor(
* affect the [stream]
*/
private fun ringerModeForStream(stream: Int): Flow<RingerMode?> {
- return if (stream == AudioManager.STREAM_RING) {
+ return if (
+ stream == AudioManager.STREAM_RING || stream == AudioManager.STREAM_NOTIFICATION
+ ) {
audioVolumeInteractor.ringerMode
} else {
flowOf(null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
deleted file mode 100644
index 134c40da1033..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.android.systemui.bouncer.data.repository
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.time.SystemClock
-import kotlinx.coroutines.test.runTest
-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
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class KeyguardBouncerRepositoryTest : SysuiTestCase() {
-
- @Mock private lateinit var systemClock: SystemClock
- @Mock private lateinit var bouncerLogger: TableLogBuffer
-
- private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
-
- lateinit var underTest: KeyguardBouncerRepository
-
- @Before
- fun setup() {
- MockitoAnnotations.initMocks(this)
- underTest =
- KeyguardBouncerRepositoryImpl(
- systemClock,
- testScope.backgroundScope,
- bouncerLogger,
- )
- }
-
- @Test
- fun changingFlowValueTriggersLogging() =
- testScope.runTest {
- underTest.setPrimaryShow(true)
- Mockito.verify(bouncerLogger)
- .logChange(eq(""), eq("PrimaryBouncerShow"), value = eq(false), any())
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 3763282cdebc..6ac20d40f2dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -881,7 +881,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@EnableSceneContainer
public void testIsInsideScrollableRegion_noOffset() {
mStackScroller.setLeftTopRightBottom(0, 0, 1000, 2000);
- mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+ mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
MotionEvent event1 = transformEventForView(createMotionEvent(500f, 400f), mStackScroller);
assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
@@ -900,7 +900,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@EnableSceneContainer
public void testIsInsideScrollableRegion_offset() {
mStackScroller.setLeftTopRightBottom(1000, 0, 2000, 2000);
- mStackScroller.setScrimClippingShape(createScrimShape(100, 500, 900, 2000));
+ mStackScroller.setClippingShape(createScrimShape(100, 500, 900, 2000));
MotionEvent event1 = transformEventForView(createMotionEvent(1500f, 400f), mStackScroller);
assertThat(mStackScroller.isInScrollableRegion(event1)).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index d3135026ce06..437ccb6a9821 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
@@ -63,7 +63,6 @@ import com.android.systemui.unfold.SysUIUnfoldComponent
import com.android.systemui.unfold.config.UnfoldTransitionConfig
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.view.ViewUtil
@@ -75,7 +74,6 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -83,6 +81,8 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -438,25 +438,28 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Test
@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_actionDown_propagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(mContext.displayId))
+ verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
}
@Test
@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any(), any())
}
@Test
@DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
- controller.onTouch(MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
+ val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+ controller.onTouch(event)
- verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any())
+ verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any())
}
@Test
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 703e2d1db200..f95f95721ce2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -23,9 +23,18 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
- private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
override val primaryBouncerStartingDisappearAnimation =
- _primaryBouncerDisappearAnimation.asStateFlow()
+ MutableSharedFlow<Runnable?>(extraBufferCapacity = 2, replay = 1)
+
+ override fun isPrimaryBouncerStartingDisappearAnimation(): Boolean {
+ val replayCache = primaryBouncerStartingDisappearAnimation.replayCache
+ return if (!replayCache.isEmpty()) {
+ replayCache.last() != null
+ } else {
+ false
+ }
+ }
+
private val _primaryBouncerScrimmed = MutableStateFlow(false)
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncerConstants.EXPANSION_HIDDEN)
@@ -53,6 +62,8 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid)
override var bouncerDismissActionModel: BouncerDismissActionModel? = null
+ override fun isDebuggable() = true
+
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
@@ -74,7 +85,7 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos
}
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
- _primaryBouncerDisappearAnimation.value = runnable
+ primaryBouncerStartingDisappearAnimation.tryEmit(runnable)
}
override fun setPanelExpansion(panelExpansion: Float) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
index 487049740079..e30e92020706 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/ui/data/repository/FakeConfigurationRepository.kt
@@ -33,10 +33,7 @@ import kotlinx.coroutines.flow.asStateFlow
@SysUISingleton
class FakeConfigurationRepository @Inject constructor() : ConfigurationRepository {
private val _onAnyConfigurationChange =
- MutableSharedFlow<Unit>(
- replay = 1,
- onBufferOverflow = BufferOverflow.DROP_OLDEST,
- )
+ MutableSharedFlow<Unit>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val onAnyConfigurationChange: Flow<Unit> = _onAnyConfigurationChange.asSharedFlow()
private val _onConfigurationChange =
@@ -53,7 +50,7 @@ class FakeConfigurationRepository @Inject constructor() : ConfigurationRepositor
get() = _onMovedToDisplay
private val _scaleForResolution = MutableStateFlow(1f)
- override val scaleForResolution: Flow<Float> = _scaleForResolution.asStateFlow()
+ override val scaleForResolution: StateFlow<Float> = _scaleForResolution.asStateFlow()
private val pixelSizes = mutableMapOf<Int, MutableStateFlow<Int>>()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
index fb6699c44d62..91f1e1c3e0c8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
@@ -17,18 +17,16 @@
package com.android.systemui.compose
import androidx.compose.runtime.snapshots.Snapshot
-import com.android.systemui.kosmos.runCurrent
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.UnconfinedTestDispatcher
-import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
/**
* Runs the given test [block] in a [TestScope] that's set up such that the Compose snapshot state
- * is settled eagerly. This is the Compose equivalent to using an [UnconfinedTestDispatcher] or
- * using [runCurrent] a lot.
+ * writes are properly applied to the global snapshot. This is for instance necessary if your test
+ * is using `snapshotFlow {}` or any other mechanism that is observing the global snapshot.
*
- * Note that this shouldn't be needed or used in a Compose test environment.
+ * Note that this isn't needed in a Compose test environment, e.g. if you use the
+ * `Compose(Content)TestRule`.
*/
fun TestScope.runTestWithSnapshots(block: suspend TestScope.() -> Unit) {
val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
index 046284861e2b..4f3b8f3541e1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeOverlayContentViewModelKosmos.kt
@@ -20,12 +20,14 @@ import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModelFactory
+import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.quickSettingsShadeOverlayContentViewModel: QuickSettingsShadeOverlayContentViewModel by
Kosmos.Fixture {
QuickSettingsShadeOverlayContentViewModel(
shadeInteractor = shadeInteractor,
sceneInteractor = sceneInteractor,
+ notificationStackAppearanceInteractor = notificationStackAppearanceInteractor,
shadeHeaderViewModelFactory = shadeHeaderViewModelFactory,
quickSettingsContainerViewModelFactory = quickSettingsContainerViewModelFactory,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
index 636cb37adf03..aaef27d257c5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/ShadeDisplaysRepositoryKosmos.kt
@@ -23,7 +23,11 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.display.AnyExternalShadeDisplayPolicy
import com.android.systemui.shade.display.DefaultDisplayShadePolicy
import com.android.systemui.shade.display.ShadeDisplayPolicy
+import com.android.systemui.shade.display.ShadeExpansionIntent
import com.android.systemui.shade.display.StatusBarTouchShadeDisplayPolicy
+import com.android.systemui.shade.domain.interactor.notificationElement
+import com.android.systemui.shade.domain.interactor.qsElement
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.util.settings.fakeGlobalSettings
val Kosmos.defaultShadeDisplayPolicy: DefaultDisplayShadePolicy by
@@ -37,16 +41,20 @@ val Kosmos.anyExternalShadeDisplayPolicy: AnyExternalShadeDisplayPolicy by
)
}
-val Kosmos.focusBasedShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
+val Kosmos.statusBarTouchShadeDisplayPolicy: StatusBarTouchShadeDisplayPolicy by
Kosmos.Fixture {
StatusBarTouchShadeDisplayPolicy(
displayRepository = displayRepository,
backgroundScope = testScope.backgroundScope,
keyguardRepository = keyguardRepository,
shadeOnDefaultDisplayWhenLocked = false,
+ shadeInteractor = { shadeInteractor },
+ notificationElement = { notificationElement },
+ qsShadeElement = { qsElement },
)
}
-
+val Kosmos.shadeExpansionIntent: ShadeExpansionIntent by
+ Kosmos.Fixture { statusBarTouchShadeDisplayPolicy }
val Kosmos.shadeDisplaysRepository: MutableShadeDisplaysRepository by
Kosmos.Fixture {
ShadeDisplaysRepositoryImpl(
@@ -62,7 +70,7 @@ val Kosmos.shadeDisplayPolicies: Set<ShadeDisplayPolicy> by
setOf(
defaultShadeDisplayPolicy,
anyExternalShadeDisplayPolicy,
- focusBasedShadeDisplayPolicy,
+ statusBarTouchShadeDisplayPolicy,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
index 6e44df833582..923de2dcbf68 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeDisplaysInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shade.ShadeDisplayChangeLatencyTracker
import com.android.systemui.shade.ShadeWindowLayoutParams
import com.android.systemui.shade.data.repository.fakeShadeDisplaysRepository
+import com.android.systemui.shade.data.repository.shadeExpansionIntent
import java.util.Optional
import org.mockito.kotlin.any
import org.mockito.kotlin.mock
@@ -49,5 +50,6 @@ val Kosmos.shadeDisplaysInteractor by
testScope.backgroundScope.coroutineContext,
mockedShadeDisplayChangeLatencyTracker,
Optional.of(shadeExpandedStateInteractor),
+ shadeExpansionIntent,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
index 1dc7229a6506..32a30502a370 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeInteractorKosmos.kt
@@ -31,7 +31,6 @@ import com.android.systemui.statusbar.phone.dozeParameters
import com.android.systemui.statusbar.policy.data.repository.userSetupRepository
import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor
import com.android.systemui.user.domain.interactor.userSwitcherInteractor
-import org.mockito.kotlin.mock
var Kosmos.baseShadeInteractor: BaseShadeInteractor by
Kosmos.Fixture {
@@ -73,7 +72,19 @@ val Kosmos.shadeInteractorImpl by
shadeModeInteractor = shadeModeInteractor,
)
}
-var Kosmos.mockShadeInteractor: ShadeInteractor by Kosmos.Fixture { mock() }
+var Kosmos.notificationElement: NotificationShadeElement by
+ Kosmos.Fixture {
+ NotificationShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext)
+ }
+var Kosmos.qsElement: QSShadeElement by
+ Kosmos.Fixture { QSShadeElement(shadeInteractor, testScope.backgroundScope.coroutineContext) }
val Kosmos.shadeExpandedStateInteractor by
- Kosmos.Fixture { ShadeExpandedStateInteractorImpl(shadeInteractor, testScope.backgroundScope) }
+ Kosmos.Fixture {
+ ShadeExpandedStateInteractorImpl(
+ shadeInteractor,
+ testScope.backgroundScope,
+ notificationElement,
+ qsElement,
+ )
+ }
val Kosmos.fakeShadeExpandedStateInteractor by Kosmos.Fixture { FakeShadeExpandedStateInteractor() }
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 01cac4c1e030..99323dbd7cce 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
@@ -31,3 +31,8 @@ val Kosmos.footerViewModel by Fixture {
shadeInteractor = shadeInteractor,
)
}
+val Kosmos.footerViewModelFactory: FooterViewModel.Factory by Fixture {
+ object : FooterViewModel.Factory {
+ override fun create() = footerViewModel
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
index c3bc744e09b0..fbc2a21b0888 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelKosmos.kt
@@ -24,7 +24,7 @@ import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.domain.interactor.remoteInputInteractor
import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.emptyShadeViewModelFactory
-import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModel
+import com.android.systemui.statusbar.notification.footer.ui.viewmodel.footerViewModelFactory
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.notificationShelfViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackInteractor
@@ -35,7 +35,7 @@ val Kosmos.notificationListViewModel by Fixture {
NotificationListViewModel(
notificationShelfViewModel,
hideListViewModel,
- Optional.of(footerViewModel),
+ footerViewModelFactory,
emptyShadeViewModelFactory,
Optional.of(notificationListLoggerViewModel),
activeNotificationsInteractor,
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
index 6467af4355f6..c8c645f1276d 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/IpSecPacketLossDetector.java
@@ -138,6 +138,8 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
@NonNull private final Object mCancellationToken = new Object();
@NonNull private final PacketLossCalculator mPacketLossCalculator;
+ @Nullable private BroadcastReceiver mDeviceIdleReceiver;
+
@Nullable private IpSecTransformWrapper mInboundTransform;
@Nullable private IpSecTransformState mLastIpSecTransformState;
@@ -168,19 +170,21 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
// Register for system broadcasts to monitor idle mode change
final IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+
+ mDeviceIdleReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
+ intent.getAction())
+ && mPowerManager.isDeviceIdleMode()) {
+ mLastIpSecTransformState = null;
+ }
+ }
+ };
getVcnContext()
.getContext()
.registerReceiver(
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(
- intent.getAction())
- && mPowerManager.isDeviceIdleMode()) {
- mLastIpSecTransformState = null;
- }
- }
- },
+ mDeviceIdleReceiver,
intentFilter,
null /* broadcastPermission not required */,
mHandler);
@@ -338,7 +342,12 @@ public class IpSecPacketLossDetector extends NetworkMetricMonitor {
super.close();
if (mInboundTransform != null) {
- mInboundTransform.close();
+ mInboundTransform = null;
+ }
+
+ if (mDeviceIdleReceiver != null) {
+ getVcnContext().getContext().unregisterReceiver(mDeviceIdleReceiver);
+ mDeviceIdleReceiver = null;
}
}
diff --git a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
index 14853440a129..55829a5fe978 100644
--- a/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
+++ b/packages/Vcn/service-b/src/com/android/server/vcn/routeselection/NetworkMetricMonitor.java
@@ -165,7 +165,7 @@ public abstract class NetworkMetricMonitor implements AutoCloseable {
}
}
- /** Set the IpSecTransform that applied to the Network being monitored */
+ /** Set the IpSecTransform that is applied to the Network being monitored */
public void setInboundTransform(@NonNull IpSecTransform inTransform) {
setInboundTransformInternal(new IpSecTransformWrapper(inTransform));
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index d6bffcb7d21d..42385fc5bdb0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -37,6 +37,7 @@ filegroup {
":framework_native_aidl",
":gsiservice_aidl",
":installd_aidl",
+ ":mmd_aidl",
":storaged_aidl",
":vold_aidl",
],
@@ -246,6 +247,7 @@ java_library_static {
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
+ "mmd_flags_lib",
"profiling_flags_lib",
"android.adpf.sessionmanager_aidl-java",
"uprobestats_flags_java_lib",
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 6459016eec75..87222a60d82d 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -564,7 +564,8 @@ public class GestureLauncherService extends SystemService {
return Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
+ context.getResources().getInteger(
+ R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction),
userId);
}
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index 6858e2941ff9..ef769cf6217c 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -9,6 +9,7 @@ per-file DisplayThread.java = michaelwr@google.com, ogunwale@google.com
# Zram writeback
per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
+per-file ZramMaintenance.java = kawasin@google.com
# ServiceWatcher
per-file ServiceWatcher.java = sooniln@google.com
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index b7bc4e4827ef..19e7e062758a 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -41,6 +41,7 @@ import static android.os.storage.OnObbStateChangeListener.ERROR_NOT_MOUNTED;
import static android.os.storage.OnObbStateChangeListener.ERROR_PERMISSION_DENIED;
import static android.os.storage.OnObbStateChangeListener.MOUNTED;
import static android.os.storage.OnObbStateChangeListener.UNMOUNTED;
+import static android.mmd.flags.Flags.mmdEnabled;
import static com.android.internal.util.XmlUtils.readStringAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
@@ -945,12 +946,17 @@ class StorageManagerService extends IStorageManager.Stub
});
refreshZramSettings();
- // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
- String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
- if (!zramPropValue.equals("0")
- && mContext.getResources().getBoolean(
+ if (mmdEnabled()) {
+ // TODO: b/375432472 - Start zram maintenance only when zram is enabled.
+ ZramMaintenance.startZramMaintenance(mContext);
+ } else {
+ // Schedule zram writeback unless zram is disabled by persist.sys.zram_enabled
+ String zramPropValue = SystemProperties.get(ZRAM_ENABLED_PROPERTY);
+ if (!zramPropValue.equals("0")
+ && mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
- ZramWriteback.scheduleZramWriteback(mContext);
+ ZramWriteback.scheduleZramWriteback(mContext);
+ }
}
configureTranscoding();
@@ -977,7 +983,7 @@ class StorageManagerService extends IStorageManager.Stub
// sole writer.
SystemProperties.set(ZRAM_ENABLED_PROPERTY, desiredPropertyValue);
// Schedule writeback only if zram is being enabled.
- if (desiredPropertyValue.equals("1")
+ if (!mmdEnabled() && desiredPropertyValue.equals("1")
&& mContext.getResources().getBoolean(
com.android.internal.R.bool.config_zramWriteback)) {
ZramWriteback.scheduleZramWriteback(mContext);
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
new file mode 100644
index 000000000000..cdb48122e321
--- /dev/null
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.IMmd;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.util.Slog;
+
+import java.time.Duration;
+
+/**
+ * Schedules zram maintenance (e.g. zram writeback, zram recompression).
+ *
+ * <p>ZramMaintenance notifies mmd the good timing to execute zram maintenance based on:
+ *
+ * <ul>
+ * <li>Enough interval has passed.
+ * <li>The system is idle.
+ * <li>The battery is not low.
+ * </ul>
+ */
+public class ZramMaintenance extends JobService {
+ private static final String TAG = ZramMaintenance.class.getName();
+ // Job id must be unique across all clients of the same uid. ZramMaintenance uses the bug number
+ // as the job id.
+ private static final int JOB_ID = 375432472;
+ private static final ComponentName sZramMaintenance =
+ new ComponentName("android", ZramMaintenance.class.getName());
+
+ private static final String FIRST_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.first_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_FIRST_DELAY_SECONDS = 3600;
+ private static final String PERIODIC_DELAY_SECONDS_PROP =
+ "mm.zram.maintenance.periodic_delay_seconds";
+ // The default is 1 hour.
+ private static final long DEFAULT_PERIODIC_DELAY_SECONDS = 3600;
+ private static final String REQUIRE_DEVICE_IDLE_PROP =
+ "mm.zram.maintenance.require_device_idle";
+ private static final boolean DEFAULT_REQUIRE_DEVICE_IDLE =
+ true;
+ private static final String REQUIRE_BATTERY_NOT_LOW_PROP =
+ "mm.zram.maintenance.require_battry_not_low";
+ private static final boolean DEFAULT_REQUIRE_BATTERY_NOT_LOW =
+ true;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ IBinder binder = ServiceManager.getService("mmd");
+ if (binder != null) {
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ try {
+ mmd.doZramMaintenance();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to doZramMaintenance", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(this, delay);
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return false;
+ }
+
+ /**
+ * Starts periodical zram maintenance.
+ */
+ public static void startZramMaintenance(Context context) {
+ Duration delay = Duration.ofSeconds(
+ SystemProperties.getLong(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay);
+ }
+
+ private static void scheduleZramMaintenance(Context context, Duration delay) {
+ JobScheduler js = context.getSystemService(JobScheduler.class);
+
+ if (js != null) {
+ js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
+ .setMinimumLatency(delay.toMillis())
+ .setRequiresDeviceIdle(
+ SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ DEFAULT_REQUIRE_DEVICE_IDLE))
+ .setRequiresBatteryNotLow(
+ SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .build());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index c99e8c8ff799..d3a52543f321 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -155,6 +155,7 @@ public class SettingsToPropertiesMapper {
"android_core_networking",
"android_health_services",
"android_sdk",
+ "android_kernel",
"aoc",
"app_widgets",
"arc_next",
diff --git a/services/core/java/com/android/server/crashrecovery/OWNERS b/services/core/java/com/android/server/crashrecovery/OWNERS
index daa02111f71f..02df9860030d 100644
--- a/services/core/java/com/android/server/crashrecovery/OWNERS
+++ b/services/core/java/com/android/server/crashrecovery/OWNERS
@@ -1,3 +1,2 @@
-ancr@google.com
harshitmahajan@google.com
robertogil@google.com
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b530da2a5f5e..ca001b9c7e6d 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4188,6 +4188,9 @@ public final class DisplayManagerService extends SystemService {
}
}
+ /**
+ * This method must not be called with mCallback held or deadlock will ensue.
+ */
@Override
public void binderDied() {
synchronized (mCallback) {
@@ -4248,17 +4251,8 @@ public final class DisplayManagerService extends SystemService {
}
}
- return transmitDisplayEvent(displayId, event);
- }
-
- /**
- * Transmit a single display event. The client is presumed ready. Return true on success
- * and false if the client died.
- */
- private boolean transmitDisplayEvent(int displayId, @DisplayEvent int event) {
- // The client is ready to receive the event.
try {
- mCallback.onDisplayEvent(displayId, event);
+ transmitDisplayEvent(displayId, event);
return true;
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process "
@@ -4269,6 +4263,18 @@ public final class DisplayManagerService extends SystemService {
}
/**
+ * Transmit a single display event. The client is presumed ready. This throws if the
+ * client has died; callers must catch and handle the exception. The exception cannot be
+ * handled directly here because {@link #binderDied()} must not be called whilst holding
+ * the mCallback lock.
+ */
+ private void transmitDisplayEvent(int displayId, @DisplayEvent int event)
+ throws RemoteException {
+ // The client is ready to receive the event.
+ mCallback.onDisplayEvent(displayId, event);
+ }
+
+ /**
* Return true if the client is interested in this event.
*/
private boolean shouldSendDisplayEvent(@DisplayEvent int event) {
@@ -4376,27 +4382,32 @@ 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() {
- synchronized (mCallback) {
- if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
- return true;
- }
- if (!isReadyLocked()) {
- return false;
- }
- for (int i = 0; i < mPendingEvents.size(); i++) {
- Event displayEvent = mPendingEvents.get(i);
- if (DEBUG) {
- Slog.d(TAG, "Send pending display event #" + i + " "
- + displayEvent.displayId + "/"
- + displayEvent.event + " to " + mUid + "/" + mPid);
+ try {
+ synchronized (mCallback) {
+ if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
+ return true;
+ }
+ if (!isReadyLocked()) {
+ return false;
}
- if (!transmitDisplayEvent(displayEvent.displayId, displayEvent.event)) {
- Slog.d(TAG, "Drop pending events for dead process " + mPid);
- break;
+ for (int i = 0; i < mPendingEvents.size(); i++) {
+ Event displayEvent = mPendingEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " "
+ + displayEvent.displayId + "/"
+ + displayEvent.event + " to " + mUid + "/" + mPid);
+ }
+ transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
}
+ mPendingEvents.clear();
+ return true;
}
- mPendingEvents.clear();
- return true;
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify process "
+ + mPid + " that display topology changed, assuming it died.", ex);
+ binderDied();
+ return false;
+
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
index a41194b898ac..1949d103a0d6 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceUtil.java
@@ -499,9 +499,11 @@ import java.util.List;
/* package */
static HubMessage createHubMessage(Message message) {
boolean isReliable = (message.flags & Message.FLAG_REQUIRES_DELIVERY_STATUS) != 0;
- return new HubMessage.Builder(message.type, message.content)
+ HubMessage outMessage = new HubMessage.Builder(message.type, message.content)
.setResponseRequired(isReliable)
.build();
+ outMessage.setMessageSequenceNumber(message.sequenceNumber);
+ return outMessage;
}
/**
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index d6f7d3bdd4a4..23e9ac5008f7 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -545,6 +545,16 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider {
for (RoutingSessionInfo session : sessions) {
if (session == null) continue;
session = assignProviderIdForSession(session);
+
+ if (Flags.enableMirroringInMediaRouter2()) {
+ var systemSessionCallback =
+ mSystemSessionCallbacks.get(session.getOriginalId());
+ if (systemSessionCallback != null) {
+ systemSessionCallback.onSessionUpdate(session);
+ continue;
+ }
+ }
+
int sourceIndex = findSessionByIdLocked(session);
if (sourceIndex < 0) {
mSessionInfos.add(targetIndex++, session);
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
index ba98a0a9fd4e..fd1bea9ae639 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -25,8 +25,9 @@ import android.media.IRemoteDisplayProvider;
import android.media.RemoteDisplayState;
import android.os.Handler;
import android.os.IBinder;
-import android.os.RemoteException;
import android.os.IBinder.DeathRecipient;
+import android.os.Looper;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
@@ -35,10 +36,8 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.Objects;
-/**
- * Maintains a connection to a particular remote display provider service.
- */
-final class RemoteDisplayProviderProxy implements ServiceConnection {
+/** Maintains a connection to a particular remote display provider service. */
+final class RemoteDisplayProviderProxy {
private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -61,12 +60,15 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
private RemoteDisplayState mDisplayState;
private boolean mScheduledDisplayStateChangedCallback;
- public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
- int userId) {
+ private final ServiceConnection mServiceConnection =
+ new ServiceConnectionImpl();
+
+ /* package */ RemoteDisplayProviderProxy(
+ Context context, ComponentName componentName, int userId, Looper looper) {
mContext = context;
mComponentName = componentName;
mUserId = userId;
- mHandler = new Handler();
+ mHandler = new Handler(looper);
}
public void dump(PrintWriter pw, String prefix) {
@@ -190,9 +192,12 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
service.setComponent(mComponentName);
try {
- mBound = mContext.bindServiceAsUser(service, this,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(mUserId));
+ mBound =
+ mContext.bindServiceAsUser(
+ service,
+ mServiceConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ new UserHandle(mUserId));
if (!mBound && DEBUG) {
Slog.d(TAG, this + ": Bind failed");
}
@@ -212,12 +217,11 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
mBound = false;
disconnect();
- mContext.unbindService(this);
+ mContext.unbindService(mServiceConnection);
}
}
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
+ private void onServiceConnectedOnHandler(IBinder service) {
if (DEBUG) {
Slog.d(TAG, this + ": Connected");
}
@@ -241,8 +245,7 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
}
}
- @Override
- public void onServiceDisconnected(ComponentName name) {
+ private void onServiceDisconnectedOnHandler() {
if (DEBUG) {
Slog.d(TAG, this + ": Service disconnected");
}
@@ -322,6 +325,20 @@ final class RemoteDisplayProviderProxy implements ServiceConnection {
void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
}
+ // All methods in this class are called on the main thread.
+ private final class ServiceConnectionImpl implements ServiceConnection {
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mHandler.post(() -> onServiceConnectedOnHandler(service));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mHandler.post(RemoteDisplayProviderProxy.this::onServiceDisconnectedOnHandler);
+ }
+ }
+
private final class Connection implements DeathRecipient {
private final IRemoteDisplayProvider mProvider;
private final ProviderCallback mCallback;
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
index 64c451d03caa..cc03c805fef0 100644
--- a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
+++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -121,9 +121,11 @@ public final class RemoteDisplayProviderWatcher {
int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
if (sourceIndex < 0) {
RemoteDisplayProviderProxy provider =
- new RemoteDisplayProviderProxy(mContext,
- new ComponentName(serviceInfo.packageName, serviceInfo.name),
- mUserId);
+ new RemoteDisplayProviderProxy(
+ mContext,
+ new ComponentName(serviceInfo.packageName, serviceInfo.name),
+ mUserId,
+ mHandler.getLooper());
provider.start();
mProviders.add(targetIndex++, provider);
mCallback.addProvider(provider);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 8931e3a1426e..011659a616d3 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -128,8 +128,20 @@ import java.util.stream.Stream;
targetProviderProxyId, existingSession.getProviderId())) {
// The currently selected route and target route both belong to the same
// provider. We tell the provider to handle the transfer.
- targetProviderProxyRecord.requestTransfer(
- existingSession.getOriginalId(), serviceTargetRoute);
+ if (serviceTargetRoute == null) {
+ notifyRequestFailed(
+ requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ } else {
+ targetProviderProxyRecord.mProxy.transferToRoute(
+ requestId,
+ clientUserHandle,
+ clientPackageName,
+ existingSession.getOriginalId(),
+ targetProviderProxyRecord.mNewOriginalIdToSourceOriginalIdMap.get(
+ routeOriginalId),
+ transferReason);
+ }
+ return;
} else {
// The target route is handled by a provider other than the target one. We need
// to release the existing session.
@@ -429,11 +441,6 @@ import java.util.stream.Stream;
}
}
- public void requestTransfer(String sessionId, MediaRoute2Info targetRoute) {
- // TODO: Map the target route to the source route original id.
- throw new UnsupportedOperationException("TODO Implement");
- }
-
public void releaseSession(long requestId, String originalSessionId) {
mProxy.releaseSession(requestId, originalSessionId);
}
@@ -491,18 +498,19 @@ import java.util.stream.Stream;
() -> {
if (mSessionRecord != null) {
mSessionRecord.onSessionUpdate(sessionInfo);
+ } else {
+ SystemMediaSessionRecord systemMediaSessionRecord =
+ new SystemMediaSessionRecord(mProviderId, sessionInfo);
+ RoutingSessionInfo translatedSession;
+ synchronized (mLock) {
+ mSessionRecord = systemMediaSessionRecord;
+ mPackageNameToSessionRecord.put(
+ mClientPackageName, systemMediaSessionRecord);
+ mPendingSessionCreations.remove(mRequestId);
+ translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
+ }
+ onSessionOverrideUpdated(translatedSession);
}
- SystemMediaSessionRecord systemMediaSessionRecord =
- new SystemMediaSessionRecord(mProviderId, sessionInfo);
- RoutingSessionInfo translatedSession;
- synchronized (mLock) {
- mSessionRecord = systemMediaSessionRecord;
- mPackageNameToSessionRecord.put(
- mClientPackageName, systemMediaSessionRecord);
- mPendingSessionCreations.remove(mRequestId);
- translatedSession = systemMediaSessionRecord.mTranslatedSessionInfo;
- }
- onSessionOverrideUpdated(translatedSession);
});
}
@@ -546,7 +554,6 @@ import java.util.stream.Stream;
* The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system
* provider ids}.
*/
- @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
@NonNull
private RoutingSessionInfo mTranslatedSessionInfo;
@@ -559,10 +566,10 @@ import java.util.stream.Stream;
@Override
public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
- RoutingSessionInfo translatedSessionInfo = mTranslatedSessionInfo;
+ RoutingSessionInfo translatedSessionInfo = asSystemProviderSession(sessionInfo);
synchronized (mLock) {
mSourceSessionInfo = sessionInfo;
- mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
+ mTranslatedSessionInfo = translatedSessionInfo;
}
onSessionOverrideUpdated(translatedSessionInfo);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 32d3970ce549..f3eed7d22bec 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -10435,16 +10435,12 @@ public class NotificationManagerService extends SystemService {
}
private void scheduleListenerHintsChanged(int state) {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
- }
+ mHandler.removeMessages(MESSAGE_LISTENER_HINTS_CHANGED);
mHandler.obtainMessage(MESSAGE_LISTENER_HINTS_CHANGED, state, 0).sendToTarget();
}
private void scheduleInterruptionFilterChanged(int listenerInterruptionFilter) {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
- }
+ mHandler.removeMessages(MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED);
mHandler.obtainMessage(
MESSAGE_LISTENER_NOTIFICATION_FILTER_CHANGED,
listenerInterruptionFilter,
@@ -10524,14 +10520,9 @@ public class NotificationManagerService extends SystemService {
}
protected void scheduleSendRankingUpdate() {
- if (Flags.notificationReduceMessagequeueUsage()) {
+ if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
sendMessage(m);
- } else {
- if (!hasMessages(MESSAGE_SEND_RANKING_UPDATE)) {
- Message m = Message.obtain(this, MESSAGE_SEND_RANKING_UPDATE);
- sendMessage(m);
- }
}
}
@@ -10540,12 +10531,8 @@ public class NotificationManagerService extends SystemService {
if (lifetimeExtensionRefactor()) {
sendMessageDelayed(Message.obtain(this, cancelRunnable), delay);
} else {
- if (Flags.notificationReduceMessagequeueUsage()) {
+ if (!hasCallbacks(cancelRunnable)) {
sendMessage(Message.obtain(this, cancelRunnable));
- } else {
- if (!hasCallbacks(cancelRunnable)) {
- sendMessage(Message.obtain(this, cancelRunnable));
- }
}
}
}
@@ -10580,9 +10567,7 @@ public class NotificationManagerService extends SystemService {
}
public void requestSort() {
- if (!Flags.notificationReduceMessagequeueUsage()) {
- removeMessages(MESSAGE_RANKING_SORT);
- }
+ removeMessages(MESSAGE_RANKING_SORT);
Message msg = Message.obtain();
msg.what = MESSAGE_RANKING_SORT;
sendMessage(msg);
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index c1ca9c23aef5..b4a8aee66c7c 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -51,13 +51,6 @@ flag {
}
flag {
- name: "notification_reduce_messagequeue_usage"
- namespace: "systemui"
- description: "When this flag is on, NMS will no longer call removeMessage() and hasCallbacks() on Handler"
- bug: "311051285"
-}
-
-flag {
name: "notification_test"
namespace: "systemui"
description: "Timing test, no functionality"
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 3660607d8764..bf0e77e03171 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -86,8 +86,6 @@ import java.util.function.Supplier;
*/
public final class BroadcastHelper {
private static final boolean DEBUG_BROADCASTS = false;
- private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
- "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
private final UserManagerInternal mUmInternal;
private final ActivityManagerInternal mAmInternal;
@@ -398,8 +396,7 @@ public final class BroadcastHelper {
sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp,
notExportedComponentNames, packageUid, reason, userIds, instantUserIds,
broadcastAllowList, "android" /* targetPackageName */,
- new String[]{
- PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
+ null /* requiredPermissions */);
}
// Second, send the PACKAGE_CHANGED broadcast to the application itself.
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index f4d4c5be035e..b85e6894b910 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,6 +33,7 @@ import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
import static android.os.UserManager.USER_OPERATION_ERROR_UNKNOWN;
import static android.os.UserManager.USER_OPERATION_ERROR_USER_RESTRICTED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
+import static android.os.UserManager.supportsMultipleUsers;
import static android.provider.Settings.Secure.HIDE_PRIVATESPACE_ENTRY_POINT;
import static com.android.internal.app.SetScreenLockDialogActivity.EXTRA_ORIGIN_USER_ID;
@@ -1156,7 +1157,7 @@ public class UserManagerService extends IUserManager.Stub {
showHsumNotificationIfNeeded();
- if (Flags.addUiForSoundsFromBackgroundUsers()) {
+ if (shouldShowNotificationForBackgroundUserSounds()) {
new BackgroundUserSoundNotifier(mContext);
}
}
@@ -8486,6 +8487,17 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
+ * @hide
+ * 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(
+ com.android.internal.R.bool.config_showNotificationForBackgroundUserAlarms)
+ && supportsMultipleUsers();
+ }
+
+ /**
* Returns instance of {@link com.android.server.pm.UserJourneyLogger}.
*/
public UserJourneyLogger getUserJourneyLogger() {
diff --git a/services/core/java/com/android/server/power/hint/TEST_MAPPING b/services/core/java/com/android/server/power/hint/TEST_MAPPING
index fd81277e9ba4..545070050977 100644
--- a/services/core/java/com/android/server/power/hint/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/hint/TEST_MAPPING
@@ -15,22 +15,5 @@
{"exclude-annotation": "org.junit.Ignore"}
]
}
- ],
- "postsubmit": [
- {
- "name": "CtsSystemHealthTestCases",
- "options": [
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- },
- {
- "name": "CtsOsTestCases",
- "options": [
- {"include-filter": "android.os.health.cts.HeadroomTest"},
- {"exclude-annotation": "androidx.test.filters.FlakyTest"},
- {"exclude-annotation": "org.junit.Ignore"}
- ]
- }
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
index 2452dc59bea5..6798a6146ae0 100644
--- a/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
+++ b/services/core/java/com/android/server/security/authenticationpolicy/AuthenticationPolicyService.java
@@ -17,7 +17,6 @@
package com.android.server.security.authenticationpolicy;
import static android.Manifest.permission.MANAGE_SECURE_LOCK_DEVICE;
-import static android.security.Flags.disableAdaptiveAuthCounterLock;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
@@ -40,7 +39,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
-import android.provider.Settings;
import android.security.authenticationpolicy.AuthenticationPolicyManager;
import android.security.authenticationpolicy.DisableSecureLockDeviceParams;
import android.security.authenticationpolicy.EnableSecureLockDeviceParams;
@@ -253,17 +251,6 @@ public class AuthenticationPolicyService extends SystemService {
return;
}
- if (disableAdaptiveAuthCounterLock() && Build.IS_DEBUGGABLE) {
- final boolean disabled = Settings.Secure.getIntForUser(
- getContext().getContentResolver(),
- Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK,
- 0 /* default */, userId) != 0;
- if (disabled) {
- Slog.d(TAG, "not locking (disabled by user)");
- return;
- }
- }
-
//TODO: additionally consider the trust signal before locking device
lockDevice(userId);
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index ae726c15ed79..a5805043ac42 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -79,6 +79,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.pm.UserManagerService;
import com.android.server.vibrator.VibrationSession.CallerInfo;
import com.android.server.vibrator.VibrationSession.DebugInfo;
import com.android.server.vibrator.VibrationSession.Status;
@@ -200,7 +201,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibratorManagerService.this::shouldCancelOnScreenOffLocked,
Status.CANCELLED_BY_SCREEN_OFF);
}
- } else if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()
+ } else if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
maybeClearCurrentAndNextSessionsLocked(
@@ -324,7 +325,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- if (android.multiuser.Flags.addUiForSoundsFromBackgroundUsers()) {
+ if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()) {
filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND);
}
context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index dca65a183f18..a24522a5080d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -482,6 +482,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final String launchedFromPackage; // always the package who started the activity.
@Nullable
final String launchedFromFeatureId; // always the feature in launchedFromPackage
+ @LaunchSourceType
int mLaunchSourceType; // latest launch source type
final Intent intent; // the original intent that generated us
final String shortComponentName; // the short component name of the intent
@@ -2330,6 +2331,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLaunchSourceType = determineLaunchSourceType(launchFromUid, caller);
}
+ @LaunchSourceType
private int determineLaunchSourceType(int launchFromUid, WindowProcessController caller) {
if (launchFromUid == Process.SYSTEM_UID || launchFromUid == Process.ROOT_UID) {
return LAUNCH_SOURCE_TYPE_SYSTEM;
@@ -5474,7 +5476,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean canAffectSystemUiFlags() {
- return task != null && task.canAffectSystemUiFlags() && isVisible()
+ final TaskFragment taskFragment = getTaskFragment();
+ return taskFragment != null && taskFragment.canAffectSystemUiFlags()
+ && isVisible()
&& !mWaitForEnteringPinnedMode && !inPinnedWindowingMode();
}
@@ -10384,7 +10388,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* game engines wait to get focus before drawing the content of the app.
*/
boolean shouldSendCompatFakeFocus() {
- return mAppCompatController.getAppCompatFocusOverrides().shouldSendFakeFocus();
+ return mAppCompatController.getFocusOverrides().shouldSendFakeFocus();
}
boolean canCaptureSnapshot() {
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 26b7cc67876e..21628341ea62 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -100,19 +100,21 @@ class ActivitySnapshotController extends AbsAppSnapshotController<ActivityRecord
ActivitySnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
super(service);
- mSnapshotPersistQueue = persistQueue;
- mPersistInfoProvider = createPersistInfoProvider(service,
- Environment::getDataSystemCeDirectory);
- mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
- mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
- initialize(new ActivitySnapshotCache());
-
final boolean snapshotEnabled =
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots)
&& !ActivityManager.isLowRamDeviceStatic(); // Don't support Android Go
setSnapshotEnabled(snapshotEnabled);
+ mSnapshotPersistQueue = persistQueue;
+ mPersistInfoProvider = createPersistInfoProvider(service,
+ Environment::getDataSystemCeDirectory);
+ mPersister = new TaskSnapshotPersister(
+ persistQueue,
+ mPersistInfoProvider,
+ shouldDisableSnapshots());
+ mSnapshotLoader = new AppSnapshotLoader(mPersistInfoProvider);
+ initialize(new ActivitySnapshotCache());
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 12c8f9ccac7c..906befc1edcc 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -1664,6 +1664,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
activityIdleInternal(null /* idleActivity */, false /* fromTimeout */,
true /* processPausingActivities */, null /* configuration */);
+ if (rootTask.getParent() == null) {
+ // The activities in the task may already be finishing. Then the task could be removed
+ // when performing the idle check.
+ return;
+ }
+
// Reparent all the tasks to the bottom of the display
final DisplayContent toDisplay =
mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 1a6c9a1a8797..a94f6252cd68 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -119,8 +119,8 @@ class AppCompatController {
}
@NonNull
- AppCompatFocusOverrides getAppCompatFocusOverrides() {
- return mAppCompatOverrides.getAppCompatFocusOverrides();
+ AppCompatFocusOverrides getFocusOverrides() {
+ return mAppCompatOverrides.getFocusOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index f8002a589eef..2d0ff9be2133 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -33,7 +33,7 @@ public class AppCompatOverrides {
@NonNull
private final AppCompatAspectRatioOverrides mAspectRatioOverrides;
@NonNull
- private final AppCompatFocusOverrides mAppCompatFocusOverrides;
+ private final AppCompatFocusOverrides mFocusOverrides;
@NonNull
private final AppCompatResizeOverrides mResizeOverrides;
@NonNull
@@ -55,8 +55,8 @@ public class AppCompatOverrides {
mAspectRatioOverrides = new AppCompatAspectRatioOverrides(activityRecord,
appCompatConfiguration, optPropBuilder, appCompatDeviceStateQuery,
mReachabilityOverrides);
- mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
- appCompatConfiguration, optPropBuilder);
+ mFocusOverrides = new AppCompatFocusOverrides(activityRecord, appCompatConfiguration,
+ optPropBuilder);
mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
optPropBuilder);
mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
@@ -79,8 +79,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatFocusOverrides getAppCompatFocusOverrides() {
- return mAppCompatFocusOverrides;
+ AppCompatFocusOverrides getFocusOverrides() {
+ return mFocusOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index d32c31f1c1c7..5435d8f164da 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -193,7 +193,6 @@ import android.os.Message;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -2187,12 +2186,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
- /** Returns {@code true} if the screen rotation animation needs to wait for the window. */
- boolean shouldSyncRotationChange(WindowState w) {
- final AsyncRotationController controller = mAsyncRotationController;
- return controller == null || !controller.isAsync(w);
- }
-
void notifyInsetsChanged(Consumer<WindowState> dispatchInsetsChanged) {
if (mFixedRotationLaunchingApp != null) {
// The insets state of fixed rotation app is a rotated copy. Make sure the visibilities
@@ -2279,10 +2272,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!shellTransitions) {
forAllWindows(w -> {
w.seamlesslyRotateIfAllowed(transaction, oldRotation, rotation, rotateSeamlessly);
- if (!rotateSeamlessly && w.mHasSurface) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
- w.setOrientationChanging(true);
- }
}, true /* traverseTopToBottom */);
mPinnedTaskController.startSeamlessRotationIfNeeded(transaction, oldRotation, rotation);
if (!mDisplayRotation.hasSeamlessRotatingWindow()) {
@@ -5083,15 +5072,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
Slog.w(TAG_WM, "Window freeze timeout expired.");
mWmService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
- forAllWindows(w -> {
- if (!w.getOrientationChanging()) {
- return;
- }
- w.orientationChangeTimedOut();
- w.mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- Slog.w(TAG_WM, "Force clearing orientation change: " + w);
- }, true /* traverseTopToBottom */);
mWmService.mWindowPlacerLocked.performSurfacePlacement();
}
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index db058cafe5fe..fa748d3a22a5 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -212,7 +212,7 @@ class DragDropController {
surface = null;
mDragState.mPid = callerPid;
mDragState.mUid = callerUid;
- mDragState.mOriginalAlpha = alpha;
+ mDragState.mStartDragAlpha = alpha;
mDragState.mAnimatedScale = callingWin.mGlobalScale;
mDragState.mToken = dragToken;
mDragState.mStartDragDisplayContent = displayContent;
@@ -287,7 +287,7 @@ class DragDropController {
}
final SurfaceControl.Transaction transaction = mDragState.mTransaction;
- transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha);
+ transaction.setAlpha(surfaceControl, mDragState.mStartDragAlpha);
transaction.show(surfaceControl);
displayContent.reparentToOverlay(transaction, surfaceControl);
mDragState.updateDragSurfaceLocked(true /* keepHandling */,
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index d48b9b4a5d10..69f32cb7b8ea 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -82,6 +82,7 @@ import java.util.concurrent.CompletableFuture;
class DragState {
private static final long MIN_ANIMATION_DURATION_MS = 195;
private static final long MAX_ANIMATION_DURATION_MS = 375;
+ private static final float DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE = 0.75f;
private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
View.DRAG_FLAG_GLOBAL_URI_WRITE;
@@ -114,8 +115,9 @@ class DragState {
boolean mDragResult;
boolean mRelinquishDragSurfaceToDropTarget;
float mAnimatedScale = 1.0f;
- float mOriginalAlpha;
- float mOriginalDisplayX, mOriginalDisplayY;
+ float mStartDragAlpha;
+ // Coords are in display coordinates space.
+ float mStartDragDisplayX, mStartDragDisplayY;
float mCurrentDisplayX, mCurrentDisplayY;
float mThumbOffsetX, mThumbOffsetY;
InputInterceptor mInputInterceptor;
@@ -497,8 +499,8 @@ class DragState {
*/
void broadcastDragStartedLocked(final float touchX, final float touchY) {
Trace.instant(TRACE_TAG_WINDOW_MANAGER, "DragDropController#DRAG_STARTED");
- mOriginalDisplayX = mCurrentDisplayX = touchX;
- mOriginalDisplayY = mCurrentDisplayY = touchY;
+ mStartDragDisplayX = mCurrentDisplayX = touchX;
+ mStartDragDisplayY = mCurrentDisplayY = touchY;
// Cache a base-class instance of the clip metadata so that parceling
// works correctly in calling out to the apps.
@@ -809,21 +811,32 @@ class DragState {
mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
+ duration = MIN_ANIMATION_DURATION_MS;
+ } else if (Flags.enableConnectedDisplaysDnd() && mCurrentDisplayContent.getDisplayId()
+ != mStartDragDisplayContent.getDisplayId()) {
+ animator = ValueAnimator.ofPropertyValuesHolder(
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
+ mCurrentDisplayX - mThumbOffsetX, mCurrentDisplayX - mThumbOffsetX),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
+ mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY - mThumbOffsetY),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
+ DIFFERENT_DISPLAY_RETURN_ANIMATION_SCALE * mAnimatedScale),
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
duration = MIN_ANIMATION_DURATION_MS;
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
- mCurrentDisplayX - mThumbOffsetX, mOriginalDisplayX - mThumbOffsetX),
+ mCurrentDisplayX - mThumbOffsetX, mStartDragDisplayX - mThumbOffsetX),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
- mCurrentDisplayY - mThumbOffsetY, mOriginalDisplayY - mThumbOffsetY),
+ mCurrentDisplayY - mThumbOffsetY, mStartDragDisplayY - mThumbOffsetY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha,
- mOriginalAlpha / 2));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha,
+ mStartDragAlpha / 2));
- final float translateX = mOriginalDisplayX - mCurrentDisplayX;
- final float translateY = mOriginalDisplayY - mCurrentDisplayY;
+ final float translateX = mStartDragDisplayX - mCurrentDisplayX;
+ final float translateY = mStartDragDisplayY - mCurrentDisplayY;
// Adjust the duration to the travel distance.
final double travelDistance = Math.sqrt(
translateX * translateX + translateY * translateY);
@@ -853,7 +866,7 @@ class DragState {
mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale,
mAnimatedScale),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0f));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0f));
} else {
animator = ValueAnimator.ofPropertyValuesHolder(
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_X,
@@ -861,7 +874,7 @@ class DragState {
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_Y,
mCurrentDisplayY - mThumbOffsetY, mCurrentDisplayY),
PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, mAnimatedScale, 0),
- PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0));
+ PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mStartDragAlpha, 0));
}
final AnimationListener listener = new AnimationListener();
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 04f09d5fe627..7a3eb67bf94e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -205,7 +205,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// For seamless rotation cases this always stays true, as the windows complete their orientation
// changes 1 by 1 without disturbing global state.
boolean mOrientationChangeComplete = true;
- boolean mWallpaperActionPending = false;
private final Handler mHandler;
@@ -1100,10 +1099,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
}
- if ((bulkUpdateParams & SET_WALLPAPER_ACTION_PENDING) != 0) {
- mWallpaperActionPending = true;
- }
-
return doRequest;
}
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 3eb13c52cca6..5e1d7928e96d 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -231,8 +231,13 @@ class SnapshotPersistQueue {
if (next.isReady(mUserManagerInternal)) {
isReadyToWrite = true;
next.onDequeuedLocked();
- } else {
+ } else if (!mShutdown) {
mWriteQueue.addLast(next);
+ } else {
+ // User manager is locked and device is shutting down, skip writing
+ // this item.
+ next.onDequeuedLocked();
+ next = null;
}
}
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fe478c60bc32..295759c2fc7e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -468,9 +468,6 @@ class Task extends TaskFragment {
// NOTE: This value needs to be persisted with each task
private TaskDescription mTaskDescription;
- /** @see #setCanAffectSystemUiFlags */
- private boolean mCanAffectSystemUiFlags = true;
-
private static Exception sTmpException;
private boolean mForceShowForAllUsers;
@@ -3288,21 +3285,6 @@ class Task extends TaskFragment {
return isRootTask() && callback.test(this) ? this : null;
}
- /**
- * @param canAffectSystemUiFlags If false, all windows in this task can not affect SystemUI
- * flags. See {@link WindowState#canAffectSystemUiFlags()}.
- */
- void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
- mCanAffectSystemUiFlags = canAffectSystemUiFlags;
- }
-
- /**
- * @see #setCanAffectSystemUiFlags
- */
- boolean canAffectSystemUiFlags() {
- return mCanAffectSystemUiFlags;
- }
-
void dontAnimateDimExit() {
mDimmer.dontAnimateExit();
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index a031acad638f..1993053c16cd 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -409,6 +409,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private boolean mForceTranslucent = false;
+ /** @see #setCanAffectSystemUiFlags */
+ private boolean mCanAffectSystemUiFlags = true;
+
final Point mLastSurfaceSize = new Point();
private final Rect mTmpBounds = new Rect();
@@ -967,6 +970,27 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
/**
+ * @param canAffectSystemUiFlags If false, all windows in this taskfragment can not affect
+ * SystemUI flags. See
+ * {@link WindowState#canAffectSystemUiFlags()}.
+ */
+ void setCanAffectSystemUiFlags(boolean canAffectSystemUiFlags) {
+ mCanAffectSystemUiFlags = canAffectSystemUiFlags;
+ }
+
+ /**
+ * @see #setCanAffectSystemUiFlags
+ */
+ boolean canAffectSystemUiFlags() {
+ if (!mCanAffectSystemUiFlags) {
+ return false;
+ }
+ final TaskFragment parentTaskFragment =
+ getParent() != null ? getParent().asTaskFragment() : null;
+ return parentTaskFragment == null || parentTaskFragment.canAffectSystemUiFlags();
+ }
+
+ /**
* Returns the TaskFragment that is being organized, which could be this or the ascendant
* TaskFragment.
*/
diff --git a/services/core/java/com/android/server/wm/TaskPersister.java b/services/core/java/com/android/server/wm/TaskPersister.java
index d89dc0b8e81c..91cd9498a356 100644
--- a/services/core/java/com/android/server/wm/TaskPersister.java
+++ b/services/core/java/com/android/server/wm/TaskPersister.java
@@ -245,18 +245,20 @@ public class TaskPersister implements PersisterQueue.Listener {
private static String fileToString(File file) {
final String newline = System.lineSeparator();
+ BufferedReader reader = null;
try {
- BufferedReader reader = new BufferedReader(new FileReader(file));
+ reader = new BufferedReader(new FileReader(file));
StringBuffer sb = new StringBuffer((int) file.length() * 2);
String line;
while ((line = reader.readLine()) != null) {
sb.append(line + newline);
}
- reader.close();
return sb.toString();
} catch (IOException ioe) {
Slog.e(TAG, "Couldn't read file " + file.getName());
return null;
+ } finally {
+ IoUtils.closeQuietly(reader);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 7d300e98f44b..432ed1d0b61d 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -66,16 +66,19 @@ class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshot
TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
super(service);
- mPersistInfoProvider = createPersistInfoProvider(service,
- Environment::getDataSystemCeDirectory);
- mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
-
- initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
final boolean snapshotEnabled =
!service.mContext
.getResources()
.getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
setSnapshotEnabled(snapshotEnabled);
+ mPersistInfoProvider = createPersistInfoProvider(service,
+ Environment::getDataSystemCeDirectory);
+
+ mPersister = new TaskSnapshotPersister(
+ persistQueue,
+ mPersistInfoProvider,
+ shouldDisableSnapshots());
+ initialize(new TaskSnapshotCache(new AppSnapshotLoader(mPersistInfoProvider)));
}
static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 87be74ae1dd9..538fd8dc5406 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -26,6 +26,7 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.UserManagerInternal;
+import com.android.window.flags.Flags;
import java.io.File;
import java.util.Arrays;
@@ -37,6 +38,8 @@ import java.util.Arrays;
*/
class TaskSnapshotPersister extends BaseAppSnapshotPersister {
+ private final boolean mDisableSnapshots;
+
/**
* The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
* called.
@@ -45,8 +48,10 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
- PersistInfoProvider persistInfoProvider) {
+ PersistInfoProvider persistInfoProvider,
+ boolean disableSnapshots) {
super(persistQueue, persistInfoProvider);
+ mDisableSnapshots = Flags.checkDisabledSnapshotsInTaskPersister() && disableSnapshots;
}
/**
@@ -57,6 +62,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
* @param snapshot The snapshot to persist.
*/
void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
+ if (mDisableSnapshots) {
+ return;
+ }
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
super.persistSnapshot(taskId, userId, snapshot);
@@ -71,6 +79,9 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
*/
@Override
void removeSnapshot(int taskId, int userId) {
+ if (mDisableSnapshots) {
+ return;
+ }
synchronized (mLock) {
mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
super.removeSnapshot(taskId, userId);
@@ -86,7 +97,7 @@ class TaskSnapshotPersister extends BaseAppSnapshotPersister {
* model.
*/
void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
- if (runningUserIds.length == 0) {
+ if (runningUserIds.length == 0 || mDisableSnapshots) {
return;
}
synchronized (mLock) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f4a455a9c2dd..75cefdff2b0b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2890,6 +2890,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
leashReference = leashReference.getParent();
}
}
+ if (wc == leashReference
+ && sortedTargets.get(i).mWindowingMode == WINDOWING_MODE_PINNED) {
+ // If a PiP task is the only target, we wanna make sure the transition root leash
+ // is at the top in case PiP is sent to back. This is done because a pinned task is
+ // meant to be always-on-top throughout a transition.
+ leashReference = ancestor.getTopChild();
+ }
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName())
.setCallsite("Transition.calculateTransitionRoots").build();
diff --git a/services/core/java/com/android/server/wm/WindowManagerFlags.java b/services/core/java/com/android/server/wm/WindowManagerFlags.java
index 7ef8d8d0c16a..df70ed2e99a8 100644
--- a/services/core/java/com/android/server/wm/WindowManagerFlags.java
+++ b/services/core/java/com/android/server/wm/WindowManagerFlags.java
@@ -58,6 +58,8 @@ class WindowManagerFlags {
final boolean mEnsureWallpaperInTransitions;
+ final boolean mAodTransition = Flags.aodTransition();
+
/* End Available Flags */
WindowManagerFlags() {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index dd6e15b74a58..bf23e757fee5 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6407,11 +6407,6 @@ public class WindowManagerService extends IWindowManager.Stub
if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
&& mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
- // WindowsState#reportResized won't tell invisible requested window to redraw,
- // so do not set it as changing orientation to avoid affecting draw state.
- if (w.isVisibleRequested()) {
- w.setOrientationChanging(true);
- }
if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_ACTIVE;
// XXX should probably keep timeout from
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index f0f1b2e47cc8..3c3a180f4da1 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -38,6 +38,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_
import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DECOR_SURFACE_BOOSTED;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
@@ -1862,6 +1863,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
taskFragment.setPinned(pinned);
break;
}
+ case OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS: {
+ taskFragment.setCanAffectSystemUiFlags(operation.getBooleanValue());
+
+ // Request to apply the flags.
+ mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
+ break;
+ }
}
return effects;
}
@@ -1937,6 +1945,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return false;
}
+ if ((opType == OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS)
+ && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+ final Throwable exception = new SecurityException(
+ "Only a system organizer can perform OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS."
+ );
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ opType, exception);
+ return false;
+ }
+
final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
return secondaryFragmentToken == null
|| validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 85e3d89730a3..da58470edc1e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -148,7 +148,6 @@ import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
-import static com.android.server.wm.WindowManagerService.WINDOWS_FREEZING_SCREENS_TIMEOUT;
import static com.android.server.wm.WindowStateAnimator.COMMIT_DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -592,27 +591,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
/** Completely remove from window manager after exit animation? */
boolean mRemoveOnExit;
- /**
- * Set when the orientation is changing and this window has not yet
- * been updated for the new orientation.
- */
- private boolean mOrientationChanging;
-
/** The time when the window was last requested to redraw for orientation change. */
private long mOrientationChangeRedrawRequestTime;
/**
- * Sometimes in addition to the mOrientationChanging
- * flag we report that the orientation is changing
- * due to a mismatch in current and reported configuration.
- *
- * In the case of timeout we still need to make sure we
- * leave the orientation changing state though, so we
- * use this as a special time out escape hatch.
- */
- private boolean mOrientationChangeTimedOut;
-
- /**
* The orientation during the last visible call to relayout. If our
* current orientation is different, the window can't be ready
* to be shown.
@@ -1497,8 +1479,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
- if ((configChanged || getOrientationChanging() || dragResizingChanged)
- && isVisibleRequested()) {
+ if ((configChanged || dragResizingChanged) && isVisibleRequested()) {
winAnimator.mDrawState = DRAW_PENDING;
if (mActivityRecord != null) {
mActivityRecord.clearAllDrawn();
@@ -1512,15 +1493,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
ProtoLog.v(WM_DEBUG_RESIZE, "Resizing window %s", this);
mWmService.mResizingWindows.add(this);
}
- } else if (getOrientationChanging()) {
- if (isDrawn()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation not waiting for draw in %s, surfaceController %s", this,
- winAnimator.mSurfaceControl);
- setOrientationChanging(false);
- mLastFreezeDuration = (int)(SystemClock.elapsedRealtime()
- - mWmService.mDisplayFreezeTime);
- }
}
}
@@ -1528,46 +1500,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame);
}
- boolean getOrientationChanging() {
- if (mTransitionController.isShellTransitionsEnabled()) {
- // Shell transition doesn't use the methods for display frozen state.
- return false;
- }
- // In addition to the local state flag, we must also consider the difference in the last
- // reported configuration vs. the current state. If the client code has not been informed of
- // the change, logic dependent on having finished processing the orientation, such as
- // unfreezing, could be improperly triggered.
- // TODO(b/62846907): Checking against {@link mLastReportedConfiguration} could be flaky as
- // this is not necessarily what the client has processed yet. Find a
- // better indicator consistent with the client.
- return (mOrientationChanging || (isVisible()
- && getConfiguration().orientation != getLastReportedConfiguration().orientation))
- && !mSeamlesslyRotated
- && !mOrientationChangeTimedOut;
- }
-
- void setOrientationChanging(boolean changing) {
- mOrientationChangeTimedOut = false;
- if (mOrientationChanging == changing) {
- return;
- }
- mOrientationChanging = changing;
- if (changing) {
- mLastFreezeDuration = 0;
- if (mWmService.mRoot.mOrientationChangeComplete
- && mDisplayContent.shouldSyncRotationChange(this)) {
- mWmService.mRoot.mOrientationChangeComplete = false;
- }
- } else {
- // The orientation change is completed. If it was hidden by the animation, reshow it.
- mDisplayContent.finishAsyncRotation(mToken);
- }
- }
-
- void orientationChangeTimedOut() {
- mOrientationChangeTimedOut = true;
- }
-
@Override
void onDisplayChanged(DisplayContent dc) {
if (dc != null && mDisplayContent != null && dc != mDisplayContent
@@ -3355,12 +3287,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mAppFreezing = false;
- if (mHasSurface && !getOrientationChanging()
- && mWmService.mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "set mOrientationChanging of %s", this);
- setOrientationChanging(true);
- }
mLastFreezeDuration = 0;
setDisplayLayoutNeeded();
return true;
@@ -4266,9 +4192,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (getOrientationChanging() || mAppFreezing) {
- pw.println(prefix + "mOrientationChanging=" + mOrientationChanging
- + " configOrientationChanging="
+ if (mAppFreezing) {
+ pw.println(prefix + " configOrientationChanging="
+ (getLastReportedConfiguration().orientation != getConfiguration().orientation)
+ " mAppFreezing=" + mAppFreezing);
}
@@ -5356,7 +5281,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Send information to SurfaceFlinger about the priority of the current window.
updateFrameRateSelectionPriorityIfNeeded();
updateScaleIfNeeded();
- mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+ mWinAnimator.prepareSurfaceLocked(getPendingTransaction());
applyDims();
}
super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index d973fb014e35..298580e4bb81 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -29,7 +29,6 @@ import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_DRAW;
-import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_STARTING_WINDOW;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_SURFACE_ALLOC;
import static com.android.internal.protolog.WmProtoLogGroups.WM_SHOW_TRANSACTIONS;
@@ -417,56 +416,19 @@ class WindowStateAnimator {
}
}
- void computeShownFrameLocked() {
- if (mWin.mIsWallpaper && mService.mRoot.mWallpaperActionPending) {
- return;
- } else if (mWin.isDragResizeChanged()) {
- // This window is awaiting a relayout because user just started (or ended)
- // drag-resizing. The shown frame (which affects surface size and pos)
- // should not be updated until we get next finished draw with the new surface.
- // Otherwise one or two frames rendered with old settings would be displayed
- // with new geometry.
- return;
- }
-
- if (DEBUG) {
- Slog.v(TAG, "computeShownFrameLocked: " + this
- + " not attached, mAlpha=" + mAlpha);
- }
-
- mShownAlpha = mAlpha;
- }
-
void prepareSurfaceLocked(SurfaceControl.Transaction t) {
final WindowState w = mWin;
if (!hasSurface()) {
-
- // There is no need to wait for an animation change if our window is gone for layout
- // already as we'll never be visible.
- if (w.getOrientationChanging() && w.isGoneForLayout()) {
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change skips hidden %s", w);
- w.setOrientationChanging(false);
- }
return;
}
- computeShownFrameLocked();
+ mShownAlpha = mAlpha;
if (!w.isOnScreen()) {
hide(t, "prepareSurfaceLocked");
if (!w.mIsWallpaper || !mService.mFlags.mEnsureWallpaperInTransitions) {
mWallpaperControllerLocked.hideWallpapers(w);
}
-
- // If we are waiting for this window to handle an orientation change. If this window is
- // really hidden (gone for layout), there is no point in still waiting for it.
- // Note that this does introduce a potential glitch if the window becomes unhidden
- // before it has drawn for the new orientation.
- if (w.getOrientationChanging() && w.isGoneForLayout()) {
- w.setOrientationChanging(false);
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation change skips hidden %s", w);
- }
} else if (mLastAlpha != mShownAlpha
|| mLastHidden) {
mLastAlpha = mShownAlpha;
@@ -483,20 +445,6 @@ class WindowStateAnimator {
}
}
}
-
- if (w.getOrientationChanging()) {
- if (!w.isDrawn()) {
- if (w.mDisplayContent.shouldSyncRotationChange(w)) {
- w.mWmService.mRoot.mOrientationChangeComplete = false;
- mAnimator.mLastWindowFreezeSource = w;
- }
- ProtoLog.v(WM_DEBUG_ORIENTATION,
- "Orientation continue waiting for draw in %s", w);
- } else {
- w.setOrientationChanging(false);
- ProtoLog.v(WM_DEBUG_ORIENTATION, "Orientation change complete in %s", w);
- }
- }
}
private void showRobustly(SurfaceControl.Transaction t) {
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index dff718a4b7d5..a34b5115faf9 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -127,7 +127,6 @@ class WindowSurfacePlacer {
mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
loopCount--;
} while (mTraversalScheduled && loopCount > 0);
- mService.mRoot.mWallpaperActionPending = false;
}
private void performSurfacePlacementLoop() {
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
index ce4126a425c6..55d23585fb70 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java
@@ -34,8 +34,10 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.PowerManager;
import android.util.ArraySet;
import android.util.Dumpable;
+import android.util.Slog;
import android.view.Display;
import android.view.DisplayInfo;
import android.view.Surface;
@@ -44,6 +46,7 @@ import com.android.server.policy.BookStylePreferredScreenCalculator.PreferredScr
import com.android.server.policy.BookStylePreferredScreenCalculator.HingeAngle;
import com.android.server.policy.BookStylePreferredScreenCalculator.StateTransition;
import com.android.server.policy.BookStyleClosedStatePredicate.ConditionSensorListener.SensorSubscription;
+import com.android.server.policy.feature.flags.FeatureFlags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -62,12 +65,19 @@ import java.util.function.Supplier;
public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceStateProvider>,
DisplayManager.DisplayListener, Dumpable {
+ private static final String TAG = "BookStyleClosedStatePredicate";
+
private final BookStylePreferredScreenCalculator mClosedStateCalculator;
private final Handler mHandler = new Handler();
private final PostureEstimator mPostureEstimator;
private final DisplayManager mDisplayManager;
+ private final PowerManager mPowerManager;
+ private final FeatureFlags mFeatureFlags;
private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
+ @PowerManager.ScreenTimeoutPolicy
+ private volatile int mScreenTimeoutPolicy;
+
/**
* Creates {@link BookStyleClosedStatePredicate}. It is expected that the device has a pair
* of accelerometer sensors (one for each movable part of the device), see parameter
@@ -92,8 +102,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
public BookStyleClosedStatePredicate(@NonNull Context context,
@NonNull ClosedStateUpdatesListener updatesListener,
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- @NonNull List<StateTransition> stateTransitions) {
+ @NonNull List<StateTransition> stateTransitions,
+ @NonNull FeatureFlags featureFlags) {
+ mFeatureFlags = featureFlags;
mDisplayManager = context.getSystemService(DisplayManager.class);
+ mPowerManager = context.getSystemService(PowerManager.class);
mDisplayManager.registerDisplayListener(this, mHandler);
mClosedStateCalculator = new BookStylePreferredScreenCalculator(stateTransitions);
@@ -108,6 +121,23 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
}
/**
+ * Initialize the predicate, the predicate could subscribe to various data sources the data
+ * from which could be used later when calling {@link BookStyleClosedStatePredicate#test}.
+ */
+ public void init() {
+ if (mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+ try {
+ mPowerManager.addScreenTimeoutPolicyListener(DEFAULT_DISPLAY, Runnable::run,
+ new ScreenTimeoutPolicyListener());
+ } catch (IllegalStateException exception) {
+ // TODO: b/389613319 - remove after removing the screen timeout policy API flagging
+ Slog.e(TAG, "Error subscribing to the screen timeout policy changes");
+ exception.printStackTrace();
+ }
+ }
+ }
+
+ /**
* Based on the current sensor readings and current state, returns true if the device should use
* 'CLOSED' device state and false if it should not use 'CLOSED' state (e.g. could use half-open
* or open states).
@@ -119,13 +149,24 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
mPostureEstimator.onDeviceClosedStatusChanged(hingeAngle == ANGLE_0);
+ final boolean isLikelyTentOrWedgeMode = mPostureEstimator.isLikelyTentOrWedgeMode()
+ || shouldForceTentOrWedgeMode();
+
final PreferredScreen preferredScreen = mClosedStateCalculator.
- calculatePreferredScreen(hingeAngle, mPostureEstimator.isLikelyTentOrWedgeMode(),
+ calculatePreferredScreen(hingeAngle, isLikelyTentOrWedgeMode,
mPostureEstimator.isLikelyReverseWedgeMode(hingeAngle));
return preferredScreen == OUTER;
}
+ private boolean shouldForceTentOrWedgeMode() {
+ if (!mFeatureFlags.forceFoldablesTentModeWithScreenWakelock()) {
+ return false;
+ }
+
+ return mScreenTimeoutPolicy == PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON;
+ }
+
private HingeAngle hingeAngleFromFloat(float hingeAngle) {
if (hingeAngle == 0f) {
return ANGLE_0;
@@ -163,7 +204,7 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
@Override
public void dump(@NonNull PrintWriter writer, @Nullable String[] args) {
writer.println(" " + getDumpableName());
-
+ writer.println(" mScreenTimeoutPolicy=" + mScreenTimeoutPolicy);
mPostureEstimator.dump(writer, args);
mClosedStateCalculator.dump(writer, args);
}
@@ -172,6 +213,15 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt
void onClosedStateUpdated();
}
+ private class ScreenTimeoutPolicyListener implements
+ PowerManager.ScreenTimeoutPolicyListener {
+ @Override
+ public void onScreenTimeoutPolicyChanged(int screenTimeoutPolicy) {
+ // called from the binder thread
+ mScreenTimeoutPolicy = screenTimeoutPolicy;
+ }
+ }
+
/**
* Estimates if the device is going to enter wedge/tent mode based on the sensor data
*/
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
index f34ec72d7e27..dfc4ba2cfa71 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleDeviceStatePolicy.java
@@ -114,7 +114,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
mIsDualDisplayBlockingEnabled = featureFlags.enableDualDisplayBlocking();
final DeviceStatePredicateWrapper[] configuration = createConfiguration(
- leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees);
+ leftAccelerometerSensor, rightAccelerometerSensor, closeAngleDegrees, featureFlags);
mProvider = new FoldableDeviceStateProvider(mContext, sensorManager, hingeAngleSensor,
hallSensor, displayManager, configuration);
@@ -122,10 +122,10 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private DeviceStatePredicateWrapper[] createConfiguration(
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- Integer closeAngleDegrees) {
+ Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
return new DeviceStatePredicateWrapper[]{
createClosedConfiguration(leftAccelerometerSensor, rightAccelerometerSensor,
- closeAngleDegrees),
+ closeAngleDegrees, featureFlags),
createConfig(getHalfOpenedDeviceState(), /* activeStatePredicate= */
(provider) -> {
final float hingeAngle = provider.getHingeAngle();
@@ -147,7 +147,7 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
private DeviceStatePredicateWrapper createClosedConfiguration(
@Nullable Sensor leftAccelerometerSensor, @Nullable Sensor rightAccelerometerSensor,
- @Nullable Integer closeAngleDegrees) {
+ @Nullable Integer closeAngleDegrees, @NonNull FeatureFlags featureFlags) {
if (closeAngleDegrees != null) {
// Switch displays at closeAngleDegrees in both ways (folding and unfolding)
@@ -161,9 +161,12 @@ public class BookStyleDeviceStatePolicy extends DeviceStatePolicy implements
if (mEnablePostureBasedClosedState) {
// Use smart closed state predicate that will use different switch angles
// based on the device posture (e.g. wedge mode, tent mode, reverse wedge mode)
- return createConfig(getClosedDeviceState(), /* activeStatePredicate= */
- new BookStyleClosedStatePredicate(mContext, this, leftAccelerometerSensor,
- rightAccelerometerSensor, DEFAULT_STATE_TRANSITIONS));
+ final BookStyleClosedStatePredicate predicate = new BookStyleClosedStatePredicate(
+ mContext, this, leftAccelerometerSensor, rightAccelerometerSensor,
+ DEFAULT_STATE_TRANSITIONS, featureFlags);
+ return createConfig(getClosedDeviceState(),
+ /* activeStatePredicate= */ predicate,
+ /* initializer= */ predicate::init);
}
// Switch to the outer display only at 0 degrees but use TENT_MODE_SWITCH_ANGLE_DEGREES
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
index daeaa9833d78..8e749529978e 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/FoldableDeviceStateProvider.java
@@ -200,6 +200,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
}
}
+ @Override
+ public void onSystemReady() {
+ for (int i = 0; i < mConfigurations.length; i++) {
+ final DeviceStatePredicateWrapper configuration = mConfigurations[i];
+ if (configuration.mInitializer != null) {
+ configuration.mInitializer.run();
+ }
+ }
+ }
+
private void assertUniqueDeviceStateIdentifier(DeviceStatePredicateWrapper configuration) {
if (mStateConditions.get(configuration.mDeviceState.getIdentifier()) != null) {
throw new IllegalArgumentException("Device state configurations must have unique"
@@ -461,11 +471,12 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
private final DeviceState mDeviceState;
private final Predicate<FoldableDeviceStateProvider> mActiveStatePredicate;
private final Predicate<FoldableDeviceStateProvider> mAvailabilityPredicate;
+ private final Runnable mInitializer;
private DeviceStatePredicateWrapper(
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> predicate) {
- this(deviceState, predicate, ALLOWED);
+ this(deviceState, predicate, ALLOWED, /* initializer= */ null);
}
/** Create a configuration with availability and availability predicate **/
@@ -473,10 +484,28 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
@NonNull DeviceState deviceState,
@NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
@NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate) {
+ this(deviceState, activeStatePredicate, availabilityPredicate, /* initializer= */ null);
+ }
+
+ /**
+ * Create a configuration with availability and availability predicate.
+ * @param deviceState specifies device state for this configuration
+ * @param activeStatePredicate predicate that should return 'true' when this device state
+ * wants to be and can be active
+ * @param availabilityPredicate predicate that should return 'true' only when this device
+ * state is allowed
+ * @param initializer callback that will be called when the system is booted and ready
+ */
+ private DeviceStatePredicateWrapper(
+ @NonNull DeviceState deviceState,
+ @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+ @NonNull Predicate<FoldableDeviceStateProvider> availabilityPredicate,
+ @Nullable Runnable initializer) {
mDeviceState = deviceState;
mActiveStatePredicate = activeStatePredicate;
mAvailabilityPredicate = availabilityPredicate;
+ mInitializer = initializer;
}
/** Create a configuration with an active state predicate **/
@@ -487,6 +516,16 @@ public final class FoldableDeviceStateProvider implements DeviceStateProvider,
return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate);
}
+ /** Create a configuration with an active state predicate and an initializer **/
+ public static DeviceStatePredicateWrapper createConfig(
+ @NonNull DeviceState deviceState,
+ @NonNull Predicate<FoldableDeviceStateProvider> activeStatePredicate,
+ @Nullable Runnable initializer
+ ) {
+ return new DeviceStatePredicateWrapper(deviceState, activeStatePredicate, ALLOWED,
+ initializer);
+ }
+
/** Create a configuration with availability and active state predicate **/
public static DeviceStatePredicateWrapper createConfig(
@NonNull DeviceState deviceState,
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 21e33dd1b99a..da2e5ee37c1a 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -9,6 +9,16 @@ flag {
}
flag {
+ name: "force_foldables_tent_mode_with_screen_wakelock"
+ namespace: "windowing_frontend"
+ description: "Switching displays on a foldable device later if screen wakelock is present"
+ bug: "363174979"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "enable_foldables_posture_based_closed_state"
namespace: "windowing_frontend"
description: "Enables smarter closed device state state for foldable devices"
diff --git a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
index 2d725d1e2294..c25d5ef09209 100644
--- a/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
+++ b/services/foldables/devicestateprovider/tests/src/com/android/server/policy/BookStyleDeviceStatePolicyTest.java
@@ -45,6 +45,8 @@ import android.hardware.SensorManager;
import android.hardware.display.DisplayManager;
import android.hardware.input.InputSensorInfo;
import android.os.Handler;
+import android.os.PowerManager;
+import android.os.PowerManager.ScreenTimeoutPolicyListener;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.view.Display;
@@ -98,6 +100,8 @@ public final class BookStyleDeviceStatePolicyTest {
@Mock
DisplayManager mDisplayManager;
@Mock
+ PowerManager mPowerManager;
+ @Mock
private Display mDisplay;
private final FakeFeatureFlagsImpl mFakeFeatureFlags = new FakeFeatureFlagsImpl();
@@ -118,6 +122,7 @@ public final class BookStyleDeviceStatePolicyTest {
private Map<Sensor, List<SensorEventListener>> mSensorEventListeners = new HashMap<>();
private DeviceStateProvider mProvider;
+ private BookStyleDeviceStatePolicy mPolicy;
@Before
public void setup() {
@@ -125,6 +130,7 @@ public final class BookStyleDeviceStatePolicyTest {
mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_FOLDABLES_POSTURE_BASED_CLOSED_STATE, true);
mFakeFeatureFlags.setFlag(Flags.FLAG_ENABLE_DUAL_DISPLAY_BLOCKING, true);
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
when(mInputSensorInfo.getName()).thenReturn("hall-effect");
mHallSensor = new Sensor(mInputSensorInfo);
@@ -146,6 +152,7 @@ public final class BookStyleDeviceStatePolicyTest {
when(mDisplayManager.getDisplay(eq(DEFAULT_DISPLAY))).thenReturn(mDisplay);
mContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+ mContext.addMockSystemService(PowerManager.class, mPowerManager);
mContext.ensureTestableResources();
when(mContext.getResources().getConfiguration()).thenReturn(mConfiguration);
@@ -592,6 +599,62 @@ public final class BookStyleDeviceStatePolicyTest {
}
@Test
+ public void test_unfoldTo85Degrees_screenWakeLockExists_forceTentModeWithWakeLockEnabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+ mPolicy.getDeviceStateProvider().onSystemReady();
+ sendHingeAngle(0f);
+ final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+ listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_KEEP_DISPLAY_ON);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(0f);
+ sendHingeAngle(15f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Keeps 'closed' state meaning that it is in 'tent' mode as we have a screen wakelock
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ }
+
+ @Test
+ public void test_unfoldTo85Degrees_noScreenWakelock_forceTentModeWithWakeLockEnabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, true);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+ mPolicy.getDeviceStateProvider().onSystemReady();
+ sendHingeAngle(0f);
+ final ScreenTimeoutPolicyListener listener = captureScreenTimeoutPolicyListener();
+ listener.onScreenTimeoutPolicyChanged(PowerManager.SCREEN_TIMEOUT_ACTIVE);
+ mProvider.setListener(mListener);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+ sendHingeAngle(180f);
+ assertLatestReportedState(DEVICE_STATE_OPENED);
+ sendHingeAngle(0f);
+ assertLatestReportedState(DEVICE_STATE_CLOSED);
+
+ sendHingeAngle(85f);
+
+ // Switches to half-opened state as we don't have a screen wakelock
+ assertLatestReportedState(DEVICE_STATE_HALF_OPENED);
+ }
+
+ @Test
+ public void test_unfoldTo85Degrees_notSubscribedToWakeLocks_forceTentModeWithWakeLockDisabled()
+ throws Exception {
+ mFakeFeatureFlags.setFlag(Flags.FLAG_FORCE_FOLDABLES_TENT_MODE_WITH_SCREEN_WAKELOCK, false);
+ mInstrumentation.runOnMainSync(() -> mProvider = createProvider());
+
+ mPolicy.getDeviceStateProvider().onSystemReady();
+
+ verify(mPowerManager, never()).addScreenTimeoutPolicyListener(anyInt(), any(), any());
+ }
+
+ @Test
public void test_foldTo10_leftSideIsFlat_keepsInnerScreenForReverseWedge() {
sendHingeAngle(180f);
sendLeftSideFlatSensorEvent(true);
@@ -751,8 +814,17 @@ public final class BookStyleDeviceStatePolicyTest {
}
private DeviceStateProvider createProvider() {
- return new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
+ mPolicy = new BookStyleDeviceStatePolicy(mFakeFeatureFlags, mContext, mHingeAngleSensor,
mHallSensor, mLeftAccelerometer, mRightAccelerometer,
- /* closeAngleDegrees= */ null).getDeviceStateProvider();
+ /* closeAngleDegrees= */ null);
+ return mPolicy.getDeviceStateProvider();
+ }
+
+ private ScreenTimeoutPolicyListener captureScreenTimeoutPolicyListener() {
+ final ArgumentCaptor<ScreenTimeoutPolicyListener> captor = ArgumentCaptor
+ .forClass(ScreenTimeoutPolicyListener.class);
+ verify(mPowerManager, atLeastOnce())
+ .addScreenTimeoutPolicyListener(anyInt(), any(), captor.capture());
+ return captor.getValue();
}
}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
index 58e4b9177808..8f26bf32335a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/BroadcastHelperTest.java
@@ -72,8 +72,6 @@ public class BroadcastHelperTest {
private static final String PACKAGE_CHANGED_TEST_PACKAGE_NAME = "testpackagename";
private static final String PACKAGE_CHANGED_TEST_MAIN_ACTIVITY =
PACKAGE_CHANGED_TEST_PACKAGE_NAME + ".MainActivity";
- private static final String PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED =
- "android.permission.INTERNAL_RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED";
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -125,34 +123,23 @@ public class BroadcastHelperTest {
@RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
@Test
- public void changeNonExportedComponent_sendPackageChangedBroadcastToSystem_withPermission()
+ public void changeNonExportedComponent_sendPackageChangedBroadcastToSystemAndApplicationItself()
throws Exception {
changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
new String[0] /* sharedPackages */);
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockActivityManagerInternal).broadcastIntentWithCallback(
- captor.capture(), eq(null),
- eq(new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED}),
- anyInt(), eq(null), eq(null), eq(null));
- Intent intent = captor.getValue();
- assertNotNull(intent);
- assertThat(intent.getPackage()).isEqualTo("android");
- }
+ ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockActivityManagerInternal, times(2)).broadcastIntentWithCallback(
+ captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
+ List<Intent> intents = captorIntent.getAllValues();
+ assertNotNull(intents);
+ assertThat(intents.size()).isEqualTo(2);
- @RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
- @Test
- public void changeNonExportedComponent_sendPackageChangedBroadcastToApplicationItself()
- throws Exception {
- changeComponentAndSendPackageChangedBroadcast(false /* changeExportedComponent */,
- new String[0] /* sharedPackages */);
+ final Intent intent1 = intents.get(0);
+ assertThat(intent1.getPackage()).isEqualTo("android");
- ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
- verify(mMockActivityManagerInternal).broadcastIntentWithCallback(captor.capture(), eq(null),
- eq(null), anyInt(), eq(null), eq(null), eq(null));
- Intent intent = captor.getValue();
- assertNotNull(intent);
- assertThat(intent.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
+ final Intent intent2 = intents.get(1);
+ assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
}
@RequiresFlagsEnabled(FLAG_REDUCE_BROADCASTS_FOR_COMPONENT_STATE_CHANGES)
@@ -163,31 +150,20 @@ public class BroadcastHelperTest {
new String[]{"shared.package"} /* sharedPackages */);
ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
- ArgumentCaptor<String[]> captorRequiredPermissions = ArgumentCaptor.forClass(
- String[].class);
verify(mMockActivityManagerInternal, times(3)).broadcastIntentWithCallback(
- captorIntent.capture(), eq(null), captorRequiredPermissions.capture(), anyInt(),
- eq(null), eq(null), eq(null));
+ captorIntent.capture(), eq(null), eq(null), anyInt(), eq(null), eq(null), eq(null));
List<Intent> intents = captorIntent.getAllValues();
- List<String[]> requiredPermissions = captorRequiredPermissions.getAllValues();
assertNotNull(intents);
assertThat(intents.size()).isEqualTo(3);
final Intent intent1 = intents.get(0);
- final String[] requiredPermission1 = requiredPermissions.get(0);
assertThat(intent1.getPackage()).isEqualTo("android");
- assertThat(requiredPermission1).isEqualTo(
- new String[]{PERMISSION_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED});
final Intent intent2 = intents.get(1);
- final String[] requiredPermission2 = requiredPermissions.get(1);
assertThat(intent2.getPackage()).isEqualTo(PACKAGE_CHANGED_TEST_PACKAGE_NAME);
- assertThat(requiredPermission2).isNull();
final Intent intent3 = intents.get(2);
- final String[] requiredPermission3 = requiredPermissions.get(2);
assertThat(intent3.getPackage()).isEqualTo("shared.package");
- assertThat(requiredPermission3).isNull();
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 92c6db5b7b96..5240f581fd9f 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -166,8 +166,7 @@ public class GestureLauncherServiceTest {
new GestureLauncherService(
mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
- withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ Settings.Secure.clearProviderForTest();
}
private WalletLaunchedReceiver registerWalletLaunchedReceiver(String action) {
@@ -223,7 +222,7 @@ public class GestureLauncherServiceTest {
withDoubleTapPowerModeConfigValue(
DOUBLE_TAP_POWER_DISABLED_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
@@ -244,7 +243,7 @@ public class GestureLauncherServiceTest {
public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
@@ -265,7 +264,7 @@ public class GestureLauncherServiceTest {
public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
@@ -286,7 +285,7 @@ public class GestureLauncherServiceTest {
public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
@@ -329,7 +328,31 @@ public class GestureLauncherServiceTest {
public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(
+ mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionCamera() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+ assertTrue(
+ mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_defaultActionNotCamera() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertFalse(
mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
@@ -341,7 +364,7 @@ public class GestureLauncherServiceTest {
public void testIsWalletDoubleTapPowerSettingEnabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertTrue(
mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -353,7 +376,7 @@ public class GestureLauncherServiceTest {
public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertFalse(
mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -365,7 +388,7 @@ public class GestureLauncherServiceTest {
public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertFalse(
mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -377,7 +400,31 @@ public class GestureLauncherServiceTest {
public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionWallet() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+
+ assertTrue(
+ mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsWalletDoubleTapPowerSettingEnabled_defaultActionNotWallet() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureActionConfig(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertFalse(
mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
@@ -1858,7 +1905,7 @@ public class GestureLauncherServiceTest {
UserHandle.USER_CURRENT);
}
- private void withDefaultDoubleTapPowerGestureAction(int action) {
+ private void withDoubleTapPowerGestureActionSettingValue(int action) {
Settings.Secure.putIntForUser(
mContentResolver,
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
@@ -1866,6 +1913,12 @@ public class GestureLauncherServiceTest {
UserHandle.USER_CURRENT);
}
+ private void withDefaultDoubleTapPowerGestureActionConfig(int action) {
+ when(mResources.getInteger(
+ com.android.internal.R.integer.config_doubleTapPowerGestureMultiTargetDefaultAction
+ )).thenReturn(action);
+ }
+
private void withEmergencyGestureEnabledConfigValue(boolean enableConfigValue) {
when(mResources.getBoolean(
com.android.internal.R.bool.config_emergencyGestureEnabled))
@@ -1931,7 +1984,7 @@ public class GestureLauncherServiceTest {
}
private void enableWalletGesture() {
- withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
@@ -1951,7 +2004,7 @@ public class GestureLauncherServiceTest {
withDoubleTapPowerModeConfigValue(
DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+ withDoubleTapPowerGestureActionSettingValue(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
} else {
withCameraDoubleTapPowerEnableConfigValue(true);
withCameraDoubleTapPowerDisableSettingValue(0);
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index 9528467f7ad1..39206dcf21ef 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -952,7 +952,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(99, si.getExtras().getInt("x"));
}
- public void testShortcutInfoSaveAndLoad() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1065,7 +1065,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
dumpUserFile(USER_11);
}
- public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1134,7 +1134,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
dumpUserFile(USER_11);
}
- public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_resId() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1211,7 +1211,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(1, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
+ public void disabled_testShortcutInfoSaveAndLoad_uri() throws InterruptedException {
mRunningUsers.put(USER_11, true);
setCaller(CALLING_PACKAGE_1, USER_11);
@@ -1299,7 +1299,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals("uri_maskable", si.getIconUri());
}
- public void testShortcutInfoSaveAndLoad_forBackup() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -1368,7 +1368,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(0, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_forBackup_resId() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup_resId() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon res32x32 = Icon.createWithResource(mClientContext, R.drawable.black_32x32);
@@ -1438,7 +1438,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertEquals(0, si.getRank());
}
- public void testShortcutInfoSaveAndLoad_forBackup_uri() {
+ public void disabled_testShortcutInfoSaveAndLoad_forBackup_uri() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon uriIcon = Icon.createWithContentUri("test_uri");
@@ -1547,7 +1547,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
- public void testShortcutInfoSaveAndLoad_intents() {
+ public void disabled_testShortcutInfoSaveAndLoad_intents() {
checkShortcutInfoSaveAndLoad_intents(new Intent(Intent.ACTION_VIEW));
mInjectedCurrentTimeMillis += INTERVAL; // reset throttling.
@@ -1789,7 +1789,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
assertFalse(mManager.setDynamicShortcuts(list(si2)));
}
- public void testThrottling_localeChanges() {
+ public void disabled_testThrottling_localeChanges() {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
@@ -2078,7 +2078,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
}
- public void testThrottling_resetByInternalCall() throws Exception {
+ public void disabled_testThrottling_resetByInternalCall() throws Exception {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
@@ -2173,7 +2173,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
});
}
- public void testReportShortcutUsed() {
+ public void disabled_testReportShortcutUsed() {
mRunningUsers.put(USER_11, true);
runWithCaller(CALLING_PACKAGE_1, USER_11, () -> {
@@ -2322,7 +2322,7 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest {
getTestContext().getPackageName()));
}
- public void testDumpCheckin() throws IOException {
+ public void disabled_testDumpCheckin() throws IOException {
prepareCrossProfileDataSet();
// prepareCrossProfileDataSet() doesn't set any icons, so do set here.
diff --git a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
index b76e0bc8cd14..ee8eb9b35088 100644
--- a/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/authenticationpolicy/AuthenticationPolicyServiceTest.java
@@ -42,10 +42,8 @@ import android.hardware.biometrics.BiometricSourceType;
import android.hardware.biometrics.events.AuthenticationFailedInfo;
import android.hardware.biometrics.events.AuthenticationSucceededInfo;
import android.os.RemoteException;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.core.app.ApplicationProvider;
@@ -153,8 +151,6 @@ public class AuthenticationPolicyServiceTest {
when(mSecureLockDeviceService.disableSecureLockDevice(any()))
.thenReturn(ERROR_UNSUPPORTED);
}
-
- toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, false /* disable */);
}
@After
@@ -256,24 +252,8 @@ public class AuthenticationPolicyServiceTest {
}
@Test
- @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
- public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockEnabled()
- throws RemoteException {
- testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
- true /* enabled */);
- }
-
- @Test
- @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
- public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked_deviceLockDisabled()
+ public void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked()
throws RemoteException {
- toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */);
- testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
- false /* enabled */);
- }
-
- private void testReportAuthAttempt_biometricAuthFailed_multiple_deviceCurrentlyNotLocked(
- boolean enabled) throws RemoteException {
// Device is currently not locked and Keyguard is not showing
when(mKeyguardManager.isDeviceLocked(PRIMARY_USER_ID)).thenReturn(false);
when(mKeyguardManager.isKeyguardLocked()).thenReturn(false);
@@ -284,11 +264,7 @@ public class AuthenticationPolicyServiceTest {
}
waitForAuthCompletion();
- if (enabled) {
- verifyLockDevice(PRIMARY_USER_ID);
- } else {
- verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID);
- }
+ verifyLockDevice(PRIMARY_USER_ID);
}
@Test
@@ -324,24 +300,8 @@ public class AuthenticationPolicyServiceTest {
}
@Test
- @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
- public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockEnabled()
+ public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser()
throws RemoteException {
- testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
- true /* enabled */);
- }
-
- @Test
- @EnableFlags({android.security.Flags.FLAG_DISABLE_ADAPTIVE_AUTH_COUNTER_LOCK})
- public void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser_deviceLockDisabled()
- throws RemoteException {
- toggleAdaptiveAuthSettingsOverride(PRIMARY_USER_ID, true /* disabled */);
- testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
- false /* enabled */);
- }
-
- private void testReportAuthAttempt_primaryAuthAndBiometricAuthFailed_primaryUser(
- boolean enabled) throws RemoteException {
// Three failed primary auth attempts
for (int i = 0; i < 3; i++) {
mLockSettingsStateListenerCaptor.getValue().onAuthenticationFailed(PRIMARY_USER_ID);
@@ -353,11 +313,7 @@ public class AuthenticationPolicyServiceTest {
}
waitForAuthCompletion();
- if (enabled) {
- verifyLockDevice(PRIMARY_USER_ID);
- } else {
- verifyNotLockDevice(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS, PRIMARY_USER_ID);
- }
+ verifyLockDevice(PRIMARY_USER_ID);
}
@Test
@@ -410,13 +366,10 @@ public class AuthenticationPolicyServiceTest {
REASON_UNKNOWN, true, userId).build();
}
+
private AuthenticationFailedInfo authFailedInfo(int userId) {
return new AuthenticationFailedInfo.Builder(BiometricSourceType.FINGERPRINT, REASON_UNKNOWN,
userId).build();
}
- private void toggleAdaptiveAuthSettingsOverride(int userId, boolean disable) {
- Settings.Secure.putIntForUser(mContext.getContentResolver(),
- Settings.Secure.DISABLE_ADAPTIVE_AUTH_LIMIT_LOCK, disable ? 1 : 0, userId);
- }
}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 194d48a80a65..767c02bd268f 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -109,6 +109,7 @@ import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.LocalServices;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.BackgroundUserSoundNotifier;
+import com.android.server.pm.UserManagerService;
import com.android.server.vibrator.VibrationSession.Status;
import org.junit.After;
@@ -896,8 +897,8 @@ public class VibratorManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
+ assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -2758,8 +2759,8 @@ public class VibratorManagerServiceTest {
}
@Test
- @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS)
public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
+ assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL);
VibratorManagerService service = createSystemReadyService();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
index d8f845389727..5e49c8cdb917 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatFocusOverridesTest.java
@@ -188,7 +188,7 @@ public class AppCompatFocusOverridesTest extends WindowTestsBase {
}
void checkShouldSendFakeFocusOnTopActivity(boolean expected) {
- Assert.assertEquals(activity().top().mAppCompatController.getAppCompatFocusOverrides()
+ Assert.assertEquals(activity().top().mAppCompatController.getFocusOverrides()
.shouldSendFakeFocus(), expected);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index dfd10ec86a20..d76a907ba010 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1583,42 +1583,6 @@ public class DisplayContentTests extends WindowTestsBase {
is(Configuration.ORIENTATION_PORTRAIT));
}
- @Test
- public void testHybridRotationAnimation() {
- final DisplayContent displayContent = mDefaultDisplay;
- final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).build();
- final WindowState navBar = newWindowBuilder("navBar", TYPE_NAVIGATION_BAR).build();
- final WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).build();
- final WindowState[] windows = { statusBar, navBar, app };
- makeWindowVisible(windows);
- final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
- displayPolicy.addWindowLw(statusBar, statusBar.mAttrs);
- displayPolicy.addWindowLw(navBar, navBar.mAttrs);
- final ScreenRotationAnimation rotationAnim = new ScreenRotationAnimation(displayContent,
- displayContent.getRotation());
- spyOn(rotationAnim);
- // Assume that the display rotation is changed so it is frozen in preparation for animation.
- doReturn(true).when(rotationAnim).hasScreenshot();
- displayContent.getDisplayRotation().setRotation((displayContent.getRotation() + 1) % 4);
- displayContent.setRotationAnimation(rotationAnim);
- // The fade rotation animation also starts to hide some non-app windows.
- assertNotNull(displayContent.getAsyncRotationController());
- assertTrue(statusBar.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
-
- for (WindowState w : windows) {
- w.setOrientationChanging(true);
- }
- // The display only waits for the app window to unfreeze.
- assertFalse(displayContent.shouldSyncRotationChange(statusBar));
- assertFalse(displayContent.shouldSyncRotationChange(navBar));
- assertTrue(displayContent.shouldSyncRotationChange(app));
- // If all windows animated by fade rotation animation have done the orientation change,
- // the animation controller should be cleared.
- statusBar.setOrientationChanging(false);
- navBar.setOrientationChanging(false);
- assertNull(displayContent.getAsyncRotationController());
- }
-
@SetupWindows(addWindows = { W_ACTIVITY, W_WALLPAPER, W_STATUS_BAR, W_NAVIGATION_BAR,
W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 7683c66c18f3..a84b374f9487 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -3892,11 +3892,12 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
// Draw letterbox.
- mActivity.setVisible(false);
- mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
- mActivity.mDisplayContent.mOpeningApps.add(mActivity);
+ mActivity.ensureActivityConfiguration();
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
+
+ // Verify mAppCompatDisplayInsets is created after recomputing.
+ assertTrue(mActivity.providesMaxBounds());
}
private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ef58498b351c..546ecc6e38a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -36,6 +36,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TAS
import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS;
import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
@@ -1911,6 +1912,53 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
OP_TYPE_REORDER_TO_TOP_OF_TASK);
}
+ @Test
+ public void testApplyTransaction_setCanAffectSystemUiFlags() {
+ mController.unregisterOrganizer(mIOrganizer);
+ registerTaskFragmentOrganizer(mIOrganizer, true /* isSystemOrganizer */);
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragment(task);
+
+ // Setting the flag to false.
+ TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ verify(tf).setCanAffectSystemUiFlags(false);
+
+ // Setting the flag back to true.
+ operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(true).build();
+ mTransaction.addTaskFragmentOperation(tf.getFragmentToken(), operation);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ verify(tf).setCanAffectSystemUiFlags(true);
+ }
+
+ @Test
+ public void testApplyTransaction_setCanAffectSystemUiFlags_failsIfNotSystemOrganizer() {
+ final Task task = createTask(mDisplayContent);
+ final TaskFragment tf = createTaskFragment(task);
+
+ TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+ OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS).setBooleanValue(false).build();
+ mTransaction
+ .addTaskFragmentOperation(tf.getFragmentToken(), operation)
+ .setErrorCallbackToken(mErrorToken);
+
+ assertApplyTransactionAllowed(mTransaction);
+
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
+
+ assertTaskFragmentErrorTransaction(OP_TYPE_SET_CAN_AFFECT_SYSTEM_UI_FLAGS,
+ SecurityException.class);
+ }
+
@NonNull
private ActivityRecord setupUntrustedEmbeddingPipReparent() {
final int pid = Binder.getCallingPid();
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 1e0cef0514d8..1e16c97de647 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -113,7 +113,7 @@ class TaskSnapshotPersisterTestBase extends WindowTestsBase {
mSnapshotPersistQueue = new SnapshotPersistQueue();
PersistInfoProvider provider =
TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR);
- mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider);
+ mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider, false);
mLoader = new AppSnapshotLoader(provider);
mSnapshotPersistQueue.start();
}
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 1febc9fb4742..38d3d2fec942 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -684,9 +684,7 @@ public class TaskTests extends WindowTestsBase {
.setWindowingMode(WINDOWING_MODE_FREEFORM).build();
Task task = rootTask.getBottomMostTask();
task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
- DisplayInfo info = new DisplayInfo();
- display.mDisplay.getDisplayInfo(info);
- final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
+ final Rect fullScreenBounds = new Rect(display.getBounds());
final Rect freeformBounds = new Rect(fullScreenBounds);
freeformBounds.inset((int) (freeformBounds.width() * 0.2),
(int) (freeformBounds.height() * 0.2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index ab9abfc4a876..f6f473b4964d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -855,7 +855,6 @@ public class WindowStateTests extends WindowTestsBase {
startingApp.updateResizingWindowIfNeeded();
assertTrue(mWm.mResizingWindows.contains(startingApp));
assertTrue(startingApp.isDrawn());
- assertFalse(startingApp.getOrientationChanging());
// Even if the display is frozen, invisible requested window should not be affected.
mWm.startFreezingDisplay(0, 0, mDisplayContent);
@@ -873,7 +872,6 @@ public class WindowStateTests extends WindowTestsBase {
win.updateResizingWindowIfNeeded();
assertThat(mWm.mResizingWindows).contains(win);
- assertTrue(win.getOrientationChanging());
mWm.mResizingWindows.remove(win);
spyOn(win.mClient);
@@ -892,7 +890,6 @@ public class WindowStateTests extends WindowTestsBase {
// Even "resized" throws remote exception, it is still considered as reported. So the window
// shouldn't be resized again (which may block unfreeze in real case).
assertThat(mWm.mResizingWindows).doesNotContain(win);
- assertFalse(win.getOrientationChanging());
}
@Test
diff --git a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
index 5db02e376f3d..7b1e4cc62dab 100644
--- a/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
+++ b/tests/vcn/java/com/android/server/vcn/routeselection/IpSecPacketLossDetectorTest.java
@@ -38,6 +38,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -284,8 +285,11 @@ public class IpSecPacketLossDetectorTest extends NetworkEvaluationTestBase {
// Stop the monitor
mIpSecPacketLossDetector.close();
+ mIpSecPacketLossDetector.close();
verifyStopped();
- verify(mIpSecTransform).close();
+
+ verify(mIpSecTransform, never()).close();
+ verify(mContext).unregisterReceiver(any());
}
@Test