summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--boot/boot-image-profile-extra.txt21
-rw-r--r--core/api/test-current.txt8
-rw-r--r--core/java/android/app/AutomaticZenRule.java2
-rw-r--r--core/java/android/app/IUiModeManager.aidl54
-rw-r--r--core/java/android/app/IUiModeManagerCallback.aidl1
-rw-r--r--core/java/android/app/KeyguardManager.java9
-rw-r--r--core/java/android/app/Notification.java28
-rw-r--r--core/java/android/app/UiModeManager.java137
-rw-r--r--core/java/android/app/contextualsearch/flags.aconfig7
-rw-r--r--core/java/android/companion/AssociationRequest.java54
-rw-r--r--core/java/android/hardware/display/DisplayManager.java10
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java18
-rw-r--r--core/java/android/hardware/input/input_framework.aconfig8
-rw-r--r--core/java/android/os/ChildZygoteProcess.java3
-rw-r--r--core/java/android/os/CombinedMessageQueue/MessageQueue.java62
-rw-r--r--core/java/android/os/Looper.java8
-rw-r--r--core/java/android/os/Message.java16
-rw-r--r--core/java/android/os/PerfettoTrace.java41
-rw-r--r--core/java/android/os/PerfettoTrackEventExtra.java20
-rw-r--r--core/java/android/os/TestLooperManager.java28
-rw-r--r--core/java/android/os/Trace.java1
-rw-r--r--core/java/android/view/NotificationHeaderView.java19
-rw-r--r--core/java/android/view/ViewRootImpl.java69
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java1
-rw-r--r--core/java/android/window/DesktopExperienceFlags.java12
-rw-r--r--core/java/android/window/DesktopModeFlags.java94
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig21
-rw-r--r--core/java/com/android/internal/notification/NotificationChannelGroupsHelper.java186
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/AndroidRuntime.cpp11
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml20
-rw-r--r--core/res/res/drawable/accessibility_autoclick_left_click.xml25
-rw-r--r--core/res/res/drawable/accessibility_autoclick_pause.xml26
-rw-r--r--core/res/res/drawable/accessibility_autoclick_position.xml26
-rw-r--r--core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml20
-rw-r--r--core/res/res/layout/accessibility_autoclick_type_panel.xml78
-rw-r--r--core/res/res/layout/notification_2025_conversation_header.xml2
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_base.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_media.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_collapsed_messaging.xml1
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_base.xml89
-rw-r--r--core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml116
-rw-r--r--core/res/res/layout/notification_2025_template_expanded_messaging.xml3
-rw-r--r--core/res/res/values/dimens.xml15
-rw-r--r--core/res/res/values/strings.xml10
-rw-r--r--core/res/res/values/styles.xml16
-rw-r--r--core/res/res/values/symbols.xml21
-rw-r--r--core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java6
-rw-r--r--core/tests/coretests/src/android/os/PerfettoTraceTest.java87
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java115
-rw-r--r--core/tests/coretests/src/com/android/internal/notification/NotificationChannelGroupsHelperTest.java268
-rw-r--r--core/tests/coretests/src/com/android/internal/notification/OWNERS1
-rw-r--r--core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java11
-rw-r--r--libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml9
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt4
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt231
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt9
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt3
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/TestActivity.kt (renamed from packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl)17
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelperTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt4
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt2
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestSyncExecutor.kt34
-rw-r--r--libs/WindowManager/Shell/res/values/ids.xml1
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml2
-rw-r--r--libs/WindowManager/Shell/shared/Android.bp1
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java148
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleResizabilityChecker.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ResizabilityChecker.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java159
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java21
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt2
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt23
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt103
-rw-r--r--libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java204
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt56
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt48
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt88
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt124
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java19
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt26
-rw-r--r--libs/androidfw/Android.bp1
-rw-r--r--libs/androidfw/LocaleDataLookup.cpp7
-rw-r--r--libs/androidfw/TypeWrappers.cpp17
-rw-r--r--libs/androidfw/include/androidfw/TypeWrappers.h13
-rw-r--r--libs/androidfw/tests/LocaleDataLookup_bench.cpp57
-rw-r--r--libs/androidfw/tests/TypeWrappers_test.cpp12
-rw-r--r--media/java/android/media/MediaCodec.java4
-rw-r--r--media/java/android/media/RoutingSessionInfo.java12
-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/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java21
-rw-r--r--packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java23
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt6
-rw-r--r--packages/SettingsLib/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java15
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java6
-rw-r--r--packages/Shell/AndroidManifest.xml6
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt38
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt5
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt3
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt61
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt189
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt38
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java6
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt7
-rw-r--r--packages/SystemUI/res/drawable/notif_footer_btn_settings.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid.xml1
-rw-r--r--packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml1
-rw-r--r--packages/SystemUI/res/values-xlarge-land/config.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml6
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt105
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java233
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl2
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsContent.kt (renamed from packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java159
-rw-r--r--packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsContent.kt31
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt11
-rw-r--r--ravenwood/texts/ravenwood-annotation-allowed-classes.txt4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java28
-rw-r--r--services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java84
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java29
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java5
-rw-r--r--services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java20
-rw-r--r--services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java38
-rw-r--r--services/core/java/com/android/server/BootReceiver.java7
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java1
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java131
-rw-r--r--services/core/java/com/android/server/ZramMaintenance.java100
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/am/UserController.java38
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java22
-rw-r--r--services/core/java/com/android/server/biometrics/PreAuthInfo.java1
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java4
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java2
-rw-r--r--services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java91
-rw-r--r--services/core/java/com/android/server/notification/GroupHelper.java101
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java39
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java58
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerService.java156
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerServiceImpl.java11
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerSettings.java26
-rw-r--r--services/core/java/com/android/server/om/OverlayManagerShellCommand.java22
-rw-r--r--services/core/java/com/android/server/pm/UserManagerInternal.java12
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java14
-rw-r--r--services/core/java/com/android/server/slice/SlicePermissionManager.java22
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java6
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java112
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java5
-rw-r--r--services/core/java/com/android/server/wm/DesktopModeHelper.java3
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java17
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java16
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java13
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java31
-rw-r--r--services/supervision/java/com/android/server/supervision/SupervisionService.java17
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java148
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java121
-rw-r--r--services/tests/servicestests/src/com/android/server/am/UserControllerTest.java19
-rw-r--r--services/tests/servicestests/src/com/android/server/media/projection/OWNERS1
-rw-r--r--services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt14
-rw-r--r--services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt155
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java379
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java41
-rw-r--r--services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java6
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java68
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java3
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt10
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt4
-rwxr-xr-xtools/localedata/extract_icu_data.py9
370 files changed, 6990 insertions, 7640 deletions
diff --git a/boot/boot-image-profile-extra.txt b/boot/boot-image-profile-extra.txt
index fd51f9cbcee1..ced0d176f174 100644
--- a/boot/boot-image-profile-extra.txt
+++ b/boot/boot-image-profile-extra.txt
@@ -45,3 +45,24 @@ HSPLandroid/os/MessageQueue$OnFileDescriptorEventListener;->*
HSPLandroid/os/MessageQueue$StackNodeType;->*
HSPLandroid/os/MessageQueue$StateNode;->*
HSPLandroid/os/MessageQueue$TimedParkStateNode;->*
+HSPLandroid/os/PerfettoTrace$Category;->*
+HSPLandroid/os/PerfettoTrace;->*
+HSPLandroid/os/PerfettoTrackEventExtra;->*
+HSPLandroid/os/PerfettoTrackEventExtra$BuilderImpl;->*
+HSPLandroid/os/PerfettoTrackEventExtra$NoOpBuilder;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgBool;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$ArgString;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$CounterTrack;->*
+HSPLandroid/os/PerfettoTrackEventExtra$NamedTrack;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Flow;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Proto;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldInt64;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldDouble;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldString;->*
+HSPLandroid/os/PerfettoTrackEventExtra$FieldNested;->*
+HSPLandroid/os/PerfettoTrackEventExtra$Pool;->*
+HSPLandroid/os/PerfettoTrackEventExtra$RingBuffer;->*
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 85ab5ed97a38..0b0738ee14dc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -909,6 +909,14 @@ package android.companion {
method @NonNull public android.companion.AssociationInfo.Builder setTimeApproved(long);
}
+ public final class AssociationRequest implements android.os.Parcelable {
+ method public boolean isSkipRoleGrant();
+ }
+
+ public static final class AssociationRequest.Builder {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES) public android.companion.AssociationRequest.Builder setSkipRoleGrant(boolean);
+ }
+
public final class CompanionDeviceManager {
method @RequiresPermission("android.permission.MANAGE_COMPANION_DEVICES") public void enableSecureTransport(boolean);
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index e0a937156906..9d1d9c7b69de 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -162,7 +162,7 @@ public final class AutomaticZenRule implements Parcelable {
* both to fields in the rule itself (such as its name) and items with sub-fields.
* @hide
*/
- public static final int MAX_STRING_LENGTH = 1000;
+ public static final int MAX_STRING_LENGTH = 500;
/**
* The maximum string length for the trigger description rule, given UI constraints.
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 3b83024d536b..6d4ad7518546 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -51,7 +51,7 @@ interface IUiModeManager {
* Return the current running mode.
*/
int getCurrentModeType();
-
+
/**
* Sets the night mode.
* <p>
@@ -161,61 +161,69 @@ interface IUiModeManager {
boolean setNightModeActivated(boolean active);
/**
- * Returns custom start clock time
- */
+ * Returns custom start clock time
+ */
long getCustomNightModeStart();
/**
- * Sets custom start clock time
- */
+ * Sets custom start clock time
+ */
void setCustomNightModeStart(long time);
/**
- * Returns custom end clock time
- */
+ * Returns custom end clock time
+ */
long getCustomNightModeEnd();
/**
- * Sets custom end clock time
- */
+ * Sets custom end clock time
+ */
void setCustomNightModeEnd(long time);
/**
- * Sets projection state for the caller for the given projection type.
- */
+ * Sets projection state for the caller for the given projection type.
+ */
boolean requestProjection(in IBinder binder, int projectionType, String callingPackage);
/**
- * Releases projection state for the caller for the given projection type.
- */
+ * Releases projection state for the caller for the given projection type.
+ */
boolean releaseProjection(int projectionType, String callingPackage);
/**
- * Registers a listener for changes to projection state.
- */
+ * Registers a listener for changes to projection state.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
void addOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener, int projectionType);
/**
- * Unregisters a listener for changes to projection state.
- */
+ * Unregisters a listener for changes to projection state.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
void removeOnProjectionStateChangedListener(in IOnProjectionStateChangedListener listener);
/**
- * Returns packages that have currently set the given projection type.
- */
+ * Returns packages that have currently set the given projection type.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
List<String> getProjectingPackages(int projectionType);
/**
- * Returns currently set projection types.
- */
+ * Returns currently set projection types.
+ */
@EnforcePermission("READ_PROJECTION_STATE")
int getActiveProjectionTypes();
/**
- * Returns the contrast for the current user
- */
+ * Returns the contrast for the current user.
+ */
float getContrast();
+
+
+ /**
+ * Returns the force invert state.
+ *
+ * @hide
+ */
+ int getForceInvertState();
}
diff --git a/core/java/android/app/IUiModeManagerCallback.aidl b/core/java/android/app/IUiModeManagerCallback.aidl
index 47c18a8bd3cb..550779d14bab 100644
--- a/core/java/android/app/IUiModeManagerCallback.aidl
+++ b/core/java/android/app/IUiModeManagerCallback.aidl
@@ -24,4 +24,5 @@ package android.app;
*/
oneway interface IUiModeManagerCallback {
void notifyContrastChanged(float contrast);
+ void notifyForceInvertStateChanged(int forceInvertState);
}
diff --git a/core/java/android/app/KeyguardManager.java b/core/java/android/app/KeyguardManager.java
index b5ac4e78c7ad..d91838c4cc2b 100644
--- a/core/java/android/app/KeyguardManager.java
+++ b/core/java/android/app/KeyguardManager.java
@@ -205,6 +205,15 @@ public class KeyguardManager {
public static final String EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS = "check_dpm";
/**
+ * When switching to a secure user, system server will expect a callback when the UI has
+ * completed the switch.
+ *
+ * @hide
+ */
+ public static final String LOCK_ON_USER_SWITCH_CALLBACK = "onSwitchCallback";
+
+
+ /**
*
* Password lock type, see {@link #setLock}
*
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index ba4914954223..a6338fbd6b77 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5999,8 +5999,10 @@ public class Notification implements Parcelable
setHeaderlessVerticalMargins(contentView, p, hasSecondLine);
// Update margins to leave space for the top line (but not for headerless views like
- // HUNS, which use a different layout that already accounts for that).
- if (Flags.notificationsRedesignTemplates() && !p.mHeaderless) {
+ // HUNS, which use a different layout that already accounts for that). Templates that
+ // have content that will be displayed under the small icon also use a different margin.
+ if (Flags.notificationsRedesignTemplates()
+ && !p.mHeaderless && !p.mHasContentInLeftMargin) {
int margin = getContentMarginTop(mContext,
R.dimen.notification_2025_content_margin_top);
contentView.setViewLayoutMargin(R.id.notification_main_column,
@@ -7597,11 +7599,19 @@ public class Notification implements Parcelable
}
private int getCompactHeadsUpBaseLayoutResource() {
- return R.layout.notification_template_material_compact_heads_up_base;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_compact_heads_up_base;
+ } else {
+ return R.layout.notification_template_material_compact_heads_up_base;
+ }
}
private int getMessagingCompactHeadsUpLayoutResource() {
- return R.layout.notification_template_material_messaging_compact_heads_up;
+ if (Flags.notificationsRedesignTemplates()) {
+ return R.layout.notification_2025_template_compact_heads_up_messaging;
+ } else {
+ return R.layout.notification_template_material_messaging_compact_heads_up;
+ }
}
private int getExpandedBaseLayoutResource() {
@@ -9502,7 +9512,8 @@ public class Notification implements Parcelable
.text(null)
.hideLeftIcon(isOneToOne)
.hideRightIcon(hideRightIcons || isOneToOne)
- .headerTextSecondary(isHeaderless ? null : conversationTitle);
+ .headerTextSecondary(isHeaderless ? null : conversationTitle)
+ .hasContentInLeftMargin(true);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
isConversationLayout
? mBuilder.getConversationLayoutResource()
@@ -14673,6 +14684,7 @@ public class Notification implements Parcelable
Icon mPromotedPicture;
boolean mCallStyleActions;
boolean mAllowTextWithProgress;
+ boolean mHasContentInLeftMargin;
int mTitleViewId;
int mTextViewId;
@Nullable CharSequence mTitle;
@@ -14698,6 +14710,7 @@ public class Notification implements Parcelable
mPromotedPicture = null;
mCallStyleActions = false;
mAllowTextWithProgress = false;
+ mHasContentInLeftMargin = false;
mTitleViewId = R.id.title;
mTextViewId = R.id.text;
mTitle = null;
@@ -14764,6 +14777,11 @@ public class Notification implements Parcelable
return this;
}
+ public StandardTemplateParams hasContentInLeftMargin(boolean hasContentInLeftMargin) {
+ mHasContentInLeftMargin = hasContentInLeftMargin;
+ return this;
+ }
+
final StandardTemplateParams hideSnoozeButton(boolean hideSnoozeButton) {
this.mHideSnoozeButton = hideSnoozeButton;
return this;
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 57549847f05d..f6c789d51aee 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -121,6 +121,24 @@ public class UiModeManager {
}
/**
+ * Listener for the force invert state. To listen for changes to
+ * the force invert state on the device, implement this interface and
+ * register it with the system by calling {@link #addForceInvertStateChangeListener}.
+ *
+ * @hide
+ */
+ public interface ForceInvertStateChangeListener {
+
+ /**
+ * Called when the force invert state changes.
+ *
+ * @param forceInvertState The force invert state in {@link #getForceInvertState}
+ * @hide
+ */
+ void onForceInvertStateChanged(@ForceInvertType int forceInvertState);
+ }
+
+ /**
* Broadcast sent when the device's UI has switched to car mode, either
* by being placed in a car dock or explicit action of the user. After
* sending the broadcast, the system will start the intent
@@ -374,6 +392,36 @@ public class UiModeManager {
@SystemApi
public static final int MODE_NIGHT_CUSTOM_TYPE_BEDTIME = 1;
+ /** @hide */
+ @IntDef(prefix = {"Force_Invert_Type_"}, value = {
+ FORCE_INVERT_TYPE_OFF,
+ FORCE_INVERT_TYPE_DARK,
+ FORCE_INVERT_TYPE_LIGHT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ForceInvertType {}
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Do not force invert.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_OFF = 0;
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Force apps to be dark.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_DARK = 1;
+
+ /**
+ * Constant for {@link #getForceInvertState()}: Force apps to be light.
+ *
+ * @hide
+ */
+ public static final int FORCE_INVERT_TYPE_LIGHT = 2;
+
private static Globals sGlobals;
/**
@@ -405,6 +453,8 @@ public class UiModeManager {
private final IUiModeManager mService;
private final Object mGlobalsLock = new Object();
+ @ForceInvertType
+ private int mForceInvertState = FORCE_INVERT_TYPE_OFF;
private float mContrast = ContrastUtils.CONTRAST_DEFAULT_VALUE;
/**
@@ -414,16 +464,63 @@ public class UiModeManager {
private final ArrayMap<ContrastChangeListener, Executor>
mContrastChangeListeners = new ArrayMap<>();
+ private final ArrayMap<ForceInvertStateChangeListener, Executor>
+ mForceInvertStateChangeListeners = new ArrayMap<>();
+
Globals(IUiModeManager service) {
mService = service;
try {
mService.addCallback(this);
mContrast = mService.getContrast();
+ mForceInvertState = mService.getForceInvertState();
} catch (RemoteException e) {
Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
}
}
+ @ForceInvertType
+ private int getForceInvertState() {
+ synchronized (mGlobalsLock) {
+ return mForceInvertState;
+ }
+ }
+
+ private void addForceInvertStateChangeListener(ForceInvertStateChangeListener listener,
+ Executor executor) {
+ synchronized (mGlobalsLock) {
+ mForceInvertStateChangeListeners.put(listener, executor);
+ }
+ }
+
+ private void removeForceInvertStateChangeListener(ForceInvertStateChangeListener listener) {
+ synchronized (mGlobalsLock) {
+ mForceInvertStateChangeListeners.remove(listener);
+ }
+ }
+
+ @Override
+ public void notifyForceInvertStateChanged(@ForceInvertType int forceInvertState) {
+ final Map<ForceInvertStateChangeListener, Executor> listeners = new ArrayMap<>();
+ synchronized (mGlobalsLock) {
+ // if value changed in the settings, update the cached value and notify listeners
+ if (mForceInvertState == forceInvertState) {
+ return;
+ }
+
+ mForceInvertState = forceInvertState;
+ listeners.putAll(mForceInvertStateChangeListeners);
+ }
+
+ listeners.forEach((listener, executor) -> {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> listener.onForceInvertStateChanged(forceInvertState));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ });
+ }
+
private float getContrast() {
synchronized (mGlobalsLock) {
return mContrast;
@@ -1453,4 +1550,44 @@ public class UiModeManager {
Objects.requireNonNull(listener);
sGlobals.removeContrastChangeListener(listener);
}
+
+ /**
+ * Returns the force invert state for the user.
+ *
+ * @hide
+ */
+ @ForceInvertType
+ public int getForceInvertState() {
+ return sGlobals.getForceInvertState();
+ }
+
+ /**
+ * Registers a {@link ForceInvertStateChangeListener} for the current user.
+ *
+ * @param executor The executor on which the listener should be called back.
+ * @param listener The listener.
+ *
+ * @hide
+ */
+ public void addForceInvertStateChangeListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull ForceInvertStateChangeListener listener) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(listener);
+ sGlobals.addForceInvertStateChangeListener(listener, executor);
+ }
+
+ /**
+ * Unregisters a {@link ForceInvertStateChangeListener} for the current user.
+ * If the listener was not registered, does nothing and returns.
+ *
+ * @param listener The listener to unregister.
+ *
+ * @hide
+ */
+ public void removeForceInvertStateChangeListener(
+ @NonNull ForceInvertStateChangeListener listener) {
+ Objects.requireNonNull(listener);
+ sGlobals.removeForceInvertStateChangeListener(listener);
+ }
}
diff --git a/core/java/android/app/contextualsearch/flags.aconfig b/core/java/android/app/contextualsearch/flags.aconfig
index c19921dcdc61..d81ec1e8b883 100644
--- a/core/java/android/app/contextualsearch/flags.aconfig
+++ b/core/java/android/app/contextualsearch/flags.aconfig
@@ -27,7 +27,10 @@ flag {
name: "contextual_search_window_layer"
namespace: "sysui_integrations"
description: "Identify live contextual search UI to exclude from contextual search screenshot."
- bug: "372510690"
+ bug: "390176823"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
}
flag {
@@ -35,4 +38,4 @@ flag {
namespace: "sysui_integrations"
description: "Add audio playing status to the contextual search invocation intent."
bug: "372935419"
-} \ No newline at end of file
+}
diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java
index a098a6067491..67dea321a446 100644
--- a/core/java/android/companion/AssociationRequest.java
+++ b/core/java/android/companion/AssociationRequest.java
@@ -16,6 +16,7 @@
package android.companion;
+import static android.Manifest.permission.ASSOCIATE_COMPANION_DEVICES;
import static android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED;
import static com.android.internal.util.CollectionUtils.emptyIfNull;
@@ -28,7 +29,10 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.StringDef;
+import android.annotation.SuppressLint;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Icon;
import android.os.Build;
@@ -214,6 +218,11 @@ public final class AssociationRequest implements Parcelable {
private final boolean mForceConfirmation;
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ */
+ private final boolean mSkipRoleGrant;
+
+ /**
* The app package name of the application the association will belong to.
* Populated by the system.
* @hide
@@ -283,6 +292,7 @@ public final class AssociationRequest implements Parcelable {
@Nullable CharSequence displayName,
boolean selfManaged,
boolean forceConfirmation,
+ boolean skipRoleGrant,
@Nullable Icon deviceIcon) {
mSingleDevice = singleDevice;
mDeviceFilters = requireNonNull(deviceFilters);
@@ -290,6 +300,7 @@ public final class AssociationRequest implements Parcelable {
mDisplayName = displayName;
mSelfManaged = selfManaged;
mForceConfirmation = forceConfirmation;
+ mSkipRoleGrant = skipRoleGrant;
mCreationTime = System.currentTimeMillis();
mDeviceIcon = deviceIcon;
}
@@ -333,6 +344,18 @@ public final class AssociationRequest implements Parcelable {
}
/**
+ * Whether to skip the role grant, permission checks and consent dialog.
+ *
+ * @see Builder#setSkipRoleGrant(boolean)
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ public boolean isSkipRoleGrant() {
+ return mSkipRoleGrant;
+ }
+
+ /**
* Whether only a single device should match the provided filter.
*
* When scanning for a single device with a specific {@link BluetoothDeviceFilter} mac
@@ -407,6 +430,7 @@ public final class AssociationRequest implements Parcelable {
private CharSequence mDisplayName;
private boolean mSelfManaged = false;
private boolean mForceConfirmation = false;
+ private boolean mSkipRoleGrant = false;
private Icon mDeviceIcon = null;
public Builder() {}
@@ -494,6 +518,27 @@ public final class AssociationRequest implements Parcelable {
}
/**
+ * Do not attempt to grant the role corresponding to the device profile.
+ *
+ * <p>This will skip the permission checks and consent dialog but will not fail if the
+ * role cannot be granted.</p>
+ *
+ * <p>Requires that the device not to have secure lock screen and that there no locked SIM
+ * card. See {@link KeyguardManager#isKeyguardSecure()}</p>
+ *
+ * @hide
+ */
+ @RequiresPermission(ASSOCIATE_COMPANION_DEVICES)
+ @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
+ @TestApi
+ @NonNull
+ public Builder setSkipRoleGrant(boolean skipRoleGrant) {
+ checkNotUsed();
+ mSkipRoleGrant = skipRoleGrant;
+ return this;
+ }
+
+ /**
* Set the device icon for the self-managed device and to display the icon in the
* self-managed association dialog.
* <p>The given device icon will be resized to 24dp x 24dp.
@@ -521,7 +566,8 @@ public final class AssociationRequest implements Parcelable {
+ "provide the display name of the device");
}
return new AssociationRequest(mSingleDevice, emptyIfNull(mDeviceFilters),
- mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mDeviceIcon);
+ mDeviceProfile, mDisplayName, mSelfManaged, mForceConfirmation, mSkipRoleGrant,
+ mDeviceIcon);
}
}
@@ -597,6 +643,7 @@ public final class AssociationRequest implements Parcelable {
+ ", associatedDevice = " + mAssociatedDevice
+ ", selfManaged = " + mSelfManaged
+ ", forceConfirmation = " + mForceConfirmation
+ + ", skipRoleGrant = " + mSkipRoleGrant
+ ", packageName = " + mPackageName
+ ", userId = " + mUserId
+ ", deviceProfilePrivilegesDescription = " + mDeviceProfilePrivilegesDescription
@@ -617,6 +664,7 @@ public final class AssociationRequest implements Parcelable {
&& Objects.equals(mAssociatedDevice, that.mAssociatedDevice)
&& mSelfManaged == that.mSelfManaged
&& mForceConfirmation == that.mForceConfirmation
+ && mSkipRoleGrant == that.mSkipRoleGrant
&& Objects.equals(mPackageName, that.mPackageName)
&& mUserId == that.mUserId
&& Objects.equals(mDeviceProfilePrivilegesDescription,
@@ -637,6 +685,7 @@ public final class AssociationRequest implements Parcelable {
_hash = 31 * _hash + Objects.hashCode(mAssociatedDevice);
_hash = 31 * _hash + Boolean.hashCode(mSelfManaged);
_hash = 31 * _hash + Boolean.hashCode(mForceConfirmation);
+ _hash = 31 * _hash + Boolean.hashCode(mSkipRoleGrant);
_hash = 31 * _hash + Objects.hashCode(mPackageName);
_hash = 31 * _hash + mUserId;
_hash = 31 * _hash + Objects.hashCode(mDeviceProfilePrivilegesDescription);
@@ -659,6 +708,7 @@ public final class AssociationRequest implements Parcelable {
if (mAssociatedDevice != null) flg |= 0x40;
if (mPackageName != null) flg |= 0x80;
if (mDeviceProfilePrivilegesDescription != null) flg |= 0x100;
+ if (mSkipRoleGrant) flg |= 0x200;
dest.writeInt(flg);
dest.writeParcelableList(mDeviceFilters, flags);
@@ -692,6 +742,7 @@ public final class AssociationRequest implements Parcelable {
boolean selfManaged = (flg & 0x2) != 0;
boolean forceConfirmation = (flg & 0x4) != 0;
boolean skipPrompt = (flg & 0x8) != 0;
+ boolean skipRoleGrant = (flg & 0x200) != 0;
List<DeviceFilter<?>> deviceFilters = new ArrayList<>();
in.readParcelableList(deviceFilters, DeviceFilter.class.getClassLoader(),
(Class<android.companion.DeviceFilter<?>>) (Class<?>)
@@ -714,6 +765,7 @@ public final class AssociationRequest implements Parcelable {
this.mAssociatedDevice = associatedDevice;
this.mSelfManaged = selfManaged;
this.mForceConfirmation = forceConfirmation;
+ this.mSkipRoleGrant = skipRoleGrant;
this.mPackageName = packageName;
this.mUserId = userId;
com.android.internal.util.AnnotationValidations.validate(
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b7460e976313..a96de4b050a3 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -641,9 +641,6 @@ public final class DisplayManager {
* is triggered whenever the properties of a {@link android.view.Display}, such as size,
* state, density are modified.
*
- * This event is not triggered for refresh rate changes as they can change very often.
- * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}.
- *
* @see #registerDisplayListener(DisplayListener, Handler, long)
*
*/
@@ -842,9 +839,6 @@ public final class DisplayManager {
* Registers a display listener to receive notifications about when
* displays are added, removed or changed.
*
- * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)}
- * instead to subscribe for explicit events of interest
- *
* @param listener The listener to register.
* @param handler The handler on which the listener should be invoked, or null
* if the listener should be invoked on the calling thread's looper.
@@ -853,9 +847,7 @@ public final class DisplayManager {
*/
public void registerDisplayListener(DisplayListener listener, Handler handler) {
registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED
- | EVENT_TYPE_DISPLAY_CHANGED
- | EVENT_TYPE_DISPLAY_REFRESH_RATE
- | EVENT_TYPE_DISPLAY_REMOVED);
+ | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED);
}
/**
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 339dbf2c2029..b5715ed25bd9 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1766,23 +1766,29 @@ public final class DisplayManagerGlobal {
}
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED;
+ // For backward compatibility, a client subscribing to
+ // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and
+ // RR changes
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED
+ | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
}
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
+ if ((eventFlags
+ & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED;
}
- if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
- baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
- }
-
if (Flags.displayListenerPerformanceImprovements()) {
+ if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) {
+ baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE;
+ }
+
if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) {
baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE;
}
}
+
return baseEventMask;
}
}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index ae017e80966f..23722ed5bb0d 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -225,3 +225,11 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "key_event_activity_detection"
+ namespace: "input"
+ is_exported: true
+ description: "Key Event Activity Detection"
+ bug: "356412905"
+}
diff --git a/core/java/android/os/ChildZygoteProcess.java b/core/java/android/os/ChildZygoteProcess.java
index d8f825a2ee60..84fd0ca4cce9 100644
--- a/core/java/android/os/ChildZygoteProcess.java
+++ b/core/java/android/os/ChildZygoteProcess.java
@@ -67,12 +67,15 @@ public class ChildZygoteProcess extends ZygoteProcess {
if (mDead.get()) {
return true;
}
+ StrictMode.ThreadPolicy oldStrictModeThreadPolicy = StrictMode.allowThreadDiskReads();
try {
if (Os.stat("/proc/" + mPid).st_uid == mUid) {
return false;
}
} catch (ErrnoException e) {
// Do nothing, it's dead.
+ } finally {
+ StrictMode.setThreadPolicy(oldStrictModeThreadPolicy);
}
mDead.set(true);
return true;
diff --git a/core/java/android/os/CombinedMessageQueue/MessageQueue.java b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
index 877f130a8b5a..349a2f0a181d 100644
--- a/core/java/android/os/CombinedMessageQueue/MessageQueue.java
+++ b/core/java/android/os/CombinedMessageQueue/MessageQueue.java
@@ -24,8 +24,6 @@ import android.annotation.TestApi;
import android.app.ActivityThread;
import android.app.Instrumentation;
import android.compat.annotation.UnsupportedAppUsage;
-import android.os.Process;
-import android.os.UserHandle;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.ravenwood.annotation.RavenwoodRedirect;
import android.ravenwood.annotation.RavenwoodRedirectionClass;
@@ -95,6 +93,13 @@ public final class MessageQueue {
private int mAsyncMessageCount;
/**
+ * @hide
+ */
+ private final AtomicLong mMessageCount = new AtomicLong();
+ private final Thread mThread;
+ private final long mTid;
+
+ /**
* Select between two implementations of message queue. The legacy implementation is used
* by default as it provides maximum compatibility with applications and tests that
* reach into MessageQueue via the mMessages field. The concurrent implemmentation is used for
@@ -128,6 +133,8 @@ public final class MessageQueue {
mUseConcurrent = sIsProcessAllowedToUseConcurrent && !isInstrumenting();
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
+ mThread = Thread.currentThread();
+ mTid = Process.myTid();
}
private static void initIsProcessAllowedToUseConcurrent() {
@@ -218,6 +225,32 @@ public final class MessageQueue {
}
}
+ private void decAndTraceMessageCount() {
+ mMessageCount.decrementAndGet();
+ traceMessageCount();
+ }
+
+ private void incAndTraceMessageCount(Message msg, long when) {
+ mMessageCount.incrementAndGet();
+ msg.mSendingThreadName = Thread.currentThread().getName();
+ msg.mEventId.set(PerfettoTrace.getFlowId());
+
+ traceMessageCount();
+ PerfettoTrace.instant(PerfettoTrace.MQ_CATEGORY, "message_queue_send")
+ .addFlow(msg.mEventId.get())
+ .addArg("receiving_thread", mThread.getName())
+ .addArg("delay", when - SystemClock.uptimeMillis())
+ .addArg("what", msg.what)
+ .emit();
+ }
+
+ /** @hide */
+ private void traceMessageCount() {
+ PerfettoTrace.counter(PerfettoTrace.MQ_CATEGORY, mMessageCount.get())
+ .usingThreadCounterTrack(mTid, mThread.getName())
+ .emit();
+ }
+
// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
@@ -800,6 +833,7 @@ public final class MessageQueue {
Message msg = nextMessage(false, false);
if (msg != null) {
msg.markInUse();
+ decAndTraceMessageCount();
return msg;
}
@@ -909,6 +943,7 @@ public final class MessageQueue {
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
+ decAndTraceMessageCount();
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
@@ -1075,6 +1110,7 @@ public final class MessageQueue {
msg.markInUse();
msg.arg1 = token;
+ incAndTraceMessageCount(msg, when);
if (!enqueueMessageUnchecked(msg, when)) {
Log.wtf(TAG_C, "Unexpected error while adding sync barrier!");
@@ -1090,6 +1126,7 @@ public final class MessageQueue {
msg.markInUse();
msg.when = when;
msg.arg1 = token;
+ incAndTraceMessageCount(msg, when);
if (Flags.messageQueueTailTracking() && mLast != null && mLast.when <= when) {
/* Message goes to tail of list */
@@ -1196,6 +1233,7 @@ public final class MessageQueue {
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
@@ -1252,6 +1290,8 @@ public final class MessageQueue {
msg.markInUse();
msg.when = when;
+ incAndTraceMessageCount(msg, when);
+
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
@@ -1391,6 +1431,7 @@ public final class MessageQueue {
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
+ decAndTraceMessageCount();
if (TRACE) {
Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
}
@@ -1642,6 +1683,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1660,6 +1702,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1718,6 +1761,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1759,6 +1803,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1777,6 +1822,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1832,6 +1878,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1850,6 +1897,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1904,6 +1952,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1921,6 +1970,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -1976,6 +2026,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
p = n;
}
@@ -1993,6 +2044,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
n.recycleUnchecked();
+ decAndTraceMessageCount();
p.next = nn;
if (p.next == null) {
mLast = p;
@@ -2027,6 +2079,8 @@ public final class MessageQueue {
mMessages = null;
mLast = null;
mAsyncMessageCount = 0;
+ mMessageCount.set(0);
+ traceMessageCount();
}
private void removeAllFutureMessagesLocked() {
@@ -2057,6 +2111,7 @@ public final class MessageQueue {
mAsyncMessageCount--;
}
p.recycleUnchecked();
+ decAndTraceMessageCount();
} while (n != null);
}
}
@@ -2701,6 +2756,7 @@ public final class MessageQueue {
MessageNode node = new MessageNode(msg, seq);
msg.when = when;
msg.markInUse();
+ incAndTraceMessageCount(msg, when);
if (DEBUG) {
Log.d(TAG_C, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
@@ -2828,6 +2884,7 @@ public final class MessageQueue {
if (removeMatches) {
if (p.removeFromStack()) {
p.mMessage.recycleUnchecked();
+ decAndTraceMessageCount();
if (mMessageCounts.incrementCancelled()) {
nativeWake(mPtr);
}
@@ -2870,6 +2927,7 @@ public final class MessageQueue {
found = true;
if (queue.remove(msg)) {
msg.mMessage.recycleUnchecked();
+ decAndTraceMessageCount();
}
} else {
return true;
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index 2fe4871e08dd..d16e4473d55f 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -199,7 +199,12 @@ public final class Looper {
return false;
}
- // This must be in a local variable, in case a UI event sets the logger
+ PerfettoTrace.begin(PerfettoTrace.MQ_CATEGORY, "message_queue_receive")
+ .addArg("sending_thread", msg.mSendingThreadName)
+ .addTerminatingFlow(msg.mEventId.get())
+ .emit();
+
+ // This must be in a local variabe, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
@@ -289,6 +294,7 @@ public final class Looper {
+ msg.callback + " what=" + msg.what);
}
+ PerfettoTrace.end(PerfettoTrace.MQ_CATEGORY).emit();
msg.recycleUnchecked();
return true;
diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java
index 702fdc2bbaa6..b22d1774d967 100644
--- a/core/java/android/os/Message.java
+++ b/core/java/android/os/Message.java
@@ -23,6 +23,8 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.concurrent.atomic.AtomicInteger;
+
/**
*
* Defines a message containing a description and arbitrary data object that can be
@@ -37,6 +39,13 @@ import com.android.internal.annotations.VisibleForTesting;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class Message implements Parcelable {
/**
+ * For tracing
+ *
+ * @hide Only for use within the system server.
+ */
+ public final AtomicInteger mEventId = new AtomicInteger();
+
+ /**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
@@ -101,6 +110,13 @@ public final class Message implements Parcelable {
*/
public int workSourceUid = UID_NONE;
+ /**
+ * Sending thread
+ *
+ * @hide
+ */
+ public String mSendingThreadName;
+
/** If set message is in use.
* This flag is set when the message is enqueued and remains set while it
* is delivered and afterwards when it is recycled. The flag is only cleared
diff --git a/core/java/android/os/PerfettoTrace.java b/core/java/android/os/PerfettoTrace.java
index 68f1570154ff..741d542ecb3b 100644
--- a/core/java/android/os/PerfettoTrace.java
+++ b/core/java/android/os/PerfettoTrace.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -32,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class PerfettoTrace {
private static final String TAG = "PerfettoTrace";
@@ -48,11 +51,14 @@ public final class PerfettoTrace {
*/
private static final AtomicInteger sFlowEventId = new AtomicInteger();
+ public static final PerfettoTrace.Category MQ_CATEGORY = new PerfettoTrace.Category("mq");
+
/**
* Perfetto category a trace event belongs to.
* Registering a category is not sufficient to capture events within the category, it must
* also be enabled in the trace config.
*/
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class Category implements PerfettoTrackEventExtra.PerfettoPointer {
private static final NativeAllocationRegistry sRegistry =
NativeAllocationRegistry.createMalloced(
@@ -97,12 +103,16 @@ public final class PerfettoTrace {
mSeverity = severity;
mPtr = native_init(name, tag, severity);
mExtraPtr = native_get_extra_ptr(mPtr);
- sRegistry.registerNativeAllocation(this, mPtr);
+ if (!RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+ sRegistry.registerNativeAllocation(this, mPtr);
+ }
}
@FastNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_init(String name, String tag, String severity);
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_delete();
@CriticalNative
private static native void native_register(long ptr);
@@ -111,8 +121,24 @@ public final class PerfettoTrace {
@CriticalNative
private static native boolean native_is_enabled(long ptr);
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_get_extra_ptr(long ptr);
+ private static long native_init$ravenwood(String name, String tag, String severity) {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_delete$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_get_extra_ptr$ravenwood(long ptr) {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
/**
* Register the category.
*/
@@ -134,10 +160,16 @@ public final class PerfettoTrace {
/**
* Whether the category is enabled or not.
*/
+ @android.ravenwood.annotation.RavenwoodReplace
public boolean isEnabled() {
return IS_FLAG_ENABLED && native_is_enabled(mPtr);
}
+ public boolean isEnabled$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return false;
+ }
+
/**
* Whether the category is registered or not.
*/
@@ -340,4 +372,11 @@ public final class PerfettoTrace {
public static void register(boolean isBackendInProcess) {
native_register(isBackendInProcess);
}
+
+ /**
+ * Registers categories with Perfetto.
+ */
+ public static void registerCategories() {
+ MQ_CATEGORY.register();
+ }
}
diff --git a/core/java/android/os/PerfettoTrackEventExtra.java b/core/java/android/os/PerfettoTrackEventExtra.java
index e034fb3726e3..72b909e64c70 100644
--- a/core/java/android/os/PerfettoTrackEventExtra.java
+++ b/core/java/android/os/PerfettoTrackEventExtra.java
@@ -16,6 +16,8 @@
package android.os;
+import com.android.internal.ravenwood.RavenwoodEnvironment;
+
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
@@ -29,6 +31,7 @@ import java.util.function.Supplier;
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class PerfettoTrackEventExtra {
private static final int DEFAULT_EXTRA_CACHE_SIZE = 5;
private static final Builder NO_OP_BUILDER = new NoOpBuilder();
@@ -305,6 +308,7 @@ public final class PerfettoTrackEventExtra {
Builder endNested();
}
+ @android.ravenwood.annotation.RavenwoodKeepWholeClass
public static final class NoOpBuilder implements Builder {
@Override
public void emit() {}
@@ -759,7 +763,9 @@ public final class PerfettoTrackEventExtra {
private PerfettoTrackEventExtra() {
mPtr = native_init();
- sRegistry.registerNativeAllocation(this, mPtr);
+ if (!RavenwoodEnvironment.getInstance().isRunningOnRavenwood()) {
+ sRegistry.registerNativeAllocation(this, mPtr);
+ }
}
/**
@@ -1342,8 +1348,10 @@ public final class PerfettoTrackEventExtra {
}
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_init();
@CriticalNative
+ @android.ravenwood.annotation.RavenwoodReplace
private static native long native_delete();
@CriticalNative
private static native void native_add_arg(long ptr, long extraPtr);
@@ -1351,4 +1359,14 @@ public final class PerfettoTrackEventExtra {
private static native void native_clear_args(long ptr);
@FastNative
private static native void native_emit(int type, long tag, String name, long ptr);
+
+ private static long native_init$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
+
+ private static long native_delete$ravenwood() {
+ // Tracing currently completely disabled under Ravenwood
+ return 0;
+ }
}
diff --git a/core/java/android/os/TestLooperManager.java b/core/java/android/os/TestLooperManager.java
index ddfa3799706e..82bdb2280c35 100644
--- a/core/java/android/os/TestLooperManager.java
+++ b/core/java/android/os/TestLooperManager.java
@@ -18,6 +18,7 @@ import android.annotation.FlaggedApi;
import android.annotation.Nullable;
import android.util.ArraySet;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
/**
@@ -38,10 +39,13 @@ public class TestLooperManager {
private final MessageQueue mQueue;
private final Looper mLooper;
private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
+ private final boolean mLooperIsMyLooper;
+
+ // When this latch is zero, it's guaranteed that the LooperHolder Message
+ // is not in the underlying queue.
+ private final CountDownLatch mLooperHolderLatch = new CountDownLatch(1);
private boolean mReleased;
- private boolean mLooperBlocked;
- private final boolean mLooperIsMyLooper;
/**
* @hide
@@ -59,6 +63,8 @@ public class TestLooperManager {
if (!mLooperIsMyLooper) {
// Post a message that will keep the looper blocked as long as we are dispatching.
new Handler(looper).post(new LooperHolder());
+ } else {
+ mLooperHolderLatch.countDown();
}
}
@@ -222,23 +228,16 @@ public class TestLooperManager {
* is not in the underlying queue.
*/
private void waitForLooperHolder() {
- while (!mLooperIsMyLooper && !mLooperBlocked) {
- synchronized (this) {
- try {
- wait();
- } catch (InterruptedException e) {
- }
- }
+ try {
+ mLooperHolderLatch.await();
+ } catch (InterruptedException e) {
}
}
private class LooperHolder implements Runnable {
@Override
public void run() {
- synchronized (TestLooperManager.this) {
- mLooperBlocked = true;
- TestLooperManager.this.notify();
- }
+ mLooperHolderLatch.countDown();
while (!mReleased) {
try {
final MessageExecution take = mExecuteQueue.take();
@@ -248,9 +247,6 @@ public class TestLooperManager {
} catch (InterruptedException e) {
}
}
- synchronized (TestLooperManager.this) {
- mLooperBlocked = false;
- }
}
private void processMessage(MessageExecution mex) {
diff --git a/core/java/android/os/Trace.java b/core/java/android/os/Trace.java
index 09e6a45dc294..bf4c4d16fe19 100644
--- a/core/java/android/os/Trace.java
+++ b/core/java/android/os/Trace.java
@@ -544,5 +544,6 @@ public final class Trace {
*/
public static void registerWithPerfetto() {
PerfettoTrace.register(false /* isBackendInProcess */);
+ PerfettoTrace.registerCategories();
}
}
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index df680c054f56..73cd5ecd39ef 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -32,6 +32,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import android.widget.RemoteViews;
@@ -265,14 +266,20 @@ public class NotificationHeaderView extends RelativeLayout {
? R.style.TextAppearance_DeviceDefault_Notification_Title
: R.style.TextAppearance_DeviceDefault_Notification_Info;
// Most of the time, we're showing text in the minimized state
- View headerText = findViewById(R.id.header_text);
- if (headerText instanceof TextView) {
- ((TextView) headerText).setTextAppearance(styleResId);
+ if (findViewById(R.id.header_text) instanceof TextView headerText) {
+ headerText.setTextAppearance(styleResId);
+ if (notificationsRedesignTemplates()) {
+ // TODO: b/378660052 - When inlining the redesign flag, this should be updated
+ // directly in TextAppearance_DeviceDefault_Notification_Title so we won't need to
+ // override it here.
+ float textSize = getContext().getResources().getDimension(
+ R.dimen.notification_2025_title_text_size);
+ headerText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+ }
}
// If there's no summary or text, we show the app name instead of nothing
- View appNameText = findViewById(R.id.app_name_text);
- if (appNameText instanceof TextView) {
- ((TextView) appNameText).setTextAppearance(styleResId);
+ if (findViewById(R.id.app_name_text) instanceof TextView appNameText) {
+ appNameText.setTextAppearance(styleResId);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index bf34069f9445..23c43f56f2bc 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -149,6 +149,8 @@ import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ResourcesManager;
+import android.app.UiModeManager;
+import android.app.UiModeManager.ForceInvertStateChangeListener;
import android.app.WindowConfiguration;
import android.app.compat.CompatChanges;
import android.app.servertransaction.WindowStateTransactionItem;
@@ -164,7 +166,6 @@ import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.database.ContentObserver;
import android.graphics.BLASTBufferQueue;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -210,7 +211,6 @@ import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.os.Vibrator;
-import android.provider.Settings;
import android.sysprop.DisplayProperties;
import android.sysprop.ViewProperties;
import android.text.TextUtils;
@@ -463,10 +463,8 @@ public final class ViewRootImpl implements ViewParent,
private CompatOnBackInvokedCallback mCompatOnBackInvokedCallback;
@Nullable
- private ContentObserver mForceInvertObserver;
+ private ForceInvertStateChangeListener mForceInvertStateChangeListener;
- private static final int INVALID_VALUE = Integer.MIN_VALUE;
- private int mForceInvertEnabled = INVALID_VALUE;
/**
* Callback for notifying about global configuration changes.
*/
@@ -543,6 +541,8 @@ public final class ViewRootImpl implements ViewParent,
@UiContext
public final Context mContext;
+ private UiModeManager mUiModeManager;
+
@UnsupportedAppUsage
final IWindowSession mWindowSession;
@NonNull Display mDisplay;
@@ -1238,6 +1238,7 @@ public final class ViewRootImpl implements ViewParent,
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
WindowLayout windowLayout) {
mContext = context;
+ mUiModeManager = context.getSystemService(UiModeManager.class);
mWindowSession = session;
mWindowLayout = windowLayout;
mDisplay = display;
@@ -1781,23 +1782,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private boolean isForceInvertEnabled() {
- if (mForceInvertEnabled == INVALID_VALUE) {
- reloadForceInvertEnabled();
- }
- return mForceInvertEnabled == 1;
- }
-
- private void reloadForceInvertEnabled() {
- if (forceInvertColor()) {
- mForceInvertEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* def= */ 0,
- UserHandle.myUserId());
- }
- }
-
/**
* Register any kind of listeners if setView was success.
*/
@@ -1829,21 +1813,11 @@ public final class ViewRootImpl implements ViewParent,
mBasePackageName);
if (forceInvertColor()) {
- if (mForceInvertObserver == null) {
- mForceInvertObserver = new ContentObserver(mHandler) {
- @Override
- public void onChange(boolean selfChange) {
- reloadForceInvertEnabled();
- updateForceDarkMode();
- }
- };
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED
- ),
- false,
- mForceInvertObserver,
- UserHandle.myUserId());
+ if (mForceInvertStateChangeListener == null) {
+ mForceInvertStateChangeListener =
+ forceInvertState -> updateForceDarkMode();
+ mUiModeManager.addForceInvertStateChangeListener(mExecutor,
+ mForceInvertStateChangeListener);
}
}
}
@@ -1861,9 +1835,10 @@ public final class ViewRootImpl implements ViewParent,
.unregisterDisplayListener(mDisplayListener);
if (forceInvertColor()) {
- if (mForceInvertObserver != null) {
- mContext.getContentResolver().unregisterContentObserver(mForceInvertObserver);
- mForceInvertObserver = null;
+ if (mForceInvertStateChangeListener != null) {
+ mUiModeManager.removeForceInvertStateChangeListener(
+ mForceInvertStateChangeListener);
+ mForceInvertStateChangeListener = null;
}
}
@@ -2050,21 +2025,25 @@ public final class ViewRootImpl implements ViewParent,
return getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
}
- /** Returns true if force dark should be enabled according to various settings */
+ /**
+ * Determines the type of force dark to apply, considering force inversion, system night mode,
+ * and app-specific settings (including developer opt-outs).
+ *
+ * @return A {@link ForceDarkType.ForceDarkTypeDef} constant indicating the force dark type.
+ */
@VisibleForTesting
public @ForceDarkType.ForceDarkTypeDef int determineForceDarkType() {
if (forceInvertColor()) {
// Force invert ignores all developer opt-outs.
// We also ignore dark theme, since the app developer can override the user's preference
- // for dark mode in configuration.uiMode. Instead, we assume that the force invert
- // setting will be enabled at the same time dark theme is in the Settings app.
- if (isForceInvertEnabled()) {
+ // for dark mode in configuration.uiMode. Instead, we assume that both force invert and
+ // the system's dark theme are enabled.
+ if (mUiModeManager.getForceInvertState() == UiModeManager.FORCE_INVERT_TYPE_DARK) {
return ForceDarkType.FORCE_INVERT_COLOR_DARK;
}
}
boolean useAutoDark = getNightMode() == Configuration.UI_MODE_NIGHT_YES;
-
if (useAutoDark) {
boolean forceDarkAllowedDefault =
SystemProperties.getBoolean(ThreadedRenderer.DEBUG_FORCE_DARK, false);
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 4d354e0655f0..aa0111a13b8e 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -134,6 +134,7 @@ public interface ImeTracker {
ORIGIN_CLIENT,
ORIGIN_SERVER,
ORIGIN_IME,
+ ORIGIN_WM_SHELL,
})
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
diff --git a/core/java/android/window/DesktopExperienceFlags.java b/core/java/android/window/DesktopExperienceFlags.java
index 7758dea3ea8a..e0c48b03dad8 100644
--- a/core/java/android/window/DesktopExperienceFlags.java
+++ b/core/java/android/window/DesktopExperienceFlags.java
@@ -40,9 +40,7 @@ import java.util.function.BooleanSupplier;
* @hide
*/
public enum DesktopExperienceFlags {
- ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
- com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
- true),
+ // go/keep-sorted start
ACTIVITY_EMBEDDING_SUPPORT_FOR_CONNECTED_DISPLAYS(
Flags::activityEmbeddingSupportForConnectedDisplays, false),
BASE_DENSITY_FOR_EXTERNAL_DISPLAYS(
@@ -53,6 +51,9 @@ public enum DesktopExperienceFlags {
ENABLE_CONNECTED_DISPLAYS_DND(Flags::enableConnectedDisplaysDnd, false),
ENABLE_CONNECTED_DISPLAYS_PIP(Flags::enableConnectedDisplaysPip, false),
ENABLE_CONNECTED_DISPLAYS_WINDOW_DRAG(Flags::enableConnectedDisplaysWindowDrag, false),
+ ENABLE_DISPLAY_CONTENT_MODE_MANAGEMENT(
+ com.android.server.display.feature.flags.Flags::enableDisplayContentModeManagement,
+ true),
ENABLE_DISPLAY_FOCUS_IN_SHELL_TRANSITIONS(Flags::enableDisplayFocusInShellTransitions, false),
ENABLE_DISPLAY_WINDOWING_MODE_SWITCHING(Flags::enableDisplayWindowingModeSwitching, false),
ENABLE_DRAG_TO_MAXIMIZE(Flags::enableDragToMaximize, true),
@@ -63,9 +64,12 @@ public enum DesktopExperienceFlags {
false),
ENABLE_PER_DISPLAY_PACKAGE_CONTEXT_CACHE_IN_STATUSBAR_NOTIF(
Flags::enablePerDisplayPackageContextCacheInStatusbarNotif, false),
+ ENABLE_TASKBAR_CONNECTED_DISPLAYS(Flags::enableTaskbarConnectedDisplays, false),
ENTER_DESKTOP_BY_DEFAULT_ON_FREEFORM_DISPLAYS(Flags::enterDesktopByDefaultOnFreeformDisplays,
false),
- REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true);
+ REPARENT_WINDOW_TOKEN_API(Flags::reparentWindowTokenApi, true)
+ // go/keep-sorted end
+ ;
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 9468301ac996..f86a4249c0e1 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -42,62 +42,68 @@ import java.util.function.BooleanSupplier;
*/
public enum DesktopModeFlags {
// All desktop mode related flags to be overridden by developer option toggle will be added here
- ENABLE_DESKTOP_WINDOWING_MODE(
- Flags::enableDesktopWindowingMode, /* shouldOverrideByDevOption= */ true),
- ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
- ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(
- Flags::enableCaptionCompatInsetForceConsumption, true),
+ // go/keep-sorted start
+ DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
+ ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
+ ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION(Flags::enableCaptionCompatInsetForceConsumption,
+ true),
ENABLE_CAPTION_COMPAT_INSET_FORCE_CONSUMPTION_ALWAYS(
Flags::enableCaptionCompatInsetForceConsumptionAlways, true),
ENABLE_CASCADING_WINDOWS(Flags::enableCascadingWindows, true),
- ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
- ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(
- Flags::enableDesktopWindowingWallpaperActivity, true),
- ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
- ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
- ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
- ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
- ENABLE_APP_HEADER_WITH_TASK_DENSITY(Flags::enableAppHeaderWithTaskDensity, true),
- ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
- ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
- DISABLE_NON_RESIZABLE_APP_SNAP_RESIZE(Flags::disableNonResizableAppSnapResizing, true),
- ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
- ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
+ ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
+ Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
+ ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(Flags::enableDesktopAppLaunchTransitionsBugfix,
+ true),
+ ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
+ ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
+ Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
+ ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
+ Flags::skipCompatUiEducationInDesktopMode, true),
+ ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
+ ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER(
+ Flags::enableDesktopWallpaperActivityForSystemUser, true),
+ ENABLE_DESKTOP_WINDOWING_APP_TO_WEB(Flags::enableDesktopWindowingAppToWeb, true),
+ ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION(Flags::enableDesktopWindowingAppToWebEducation,
+ true),
ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION(Flags::enableDesktopWindowingBackNavigation, true),
- ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
- ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(
- Flags::enableDesktopWindowingTaskbarRunningApps, true),
- ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
- Flags::enableWindowingTransitionHandlersObservers, false),
- ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
- ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingEnterTransitionBugfix, true),
+ ENABLE_DESKTOP_WINDOWING_EXIT_BY_MINIMIZE_TRANSITION_BUGFIX(
+ Flags::enableDesktopWindowingExitByMinimizeTransitionBugfix, false),
ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX(
Flags::enableDesktopWindowingExitTransitionsBugfix, true),
- ENABLE_DESKTOP_APP_LAUNCH_ALTTAB_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchAlttabTransitionsBugfix, true),
- ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS_BUGFIX(
- Flags::enableDesktopAppLaunchTransitionsBugfix, true),
- ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(
- Flags::enableCompatUiVisibilityStatus, true),
- ENABLE_DESKTOP_SKIP_COMPAT_UI_EDUCATION_IN_DESKTOP_MODE_BUGFIX(
- Flags::skipCompatUiEducationInDesktopMode, true),
- INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
- Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true),
ENABLE_DESKTOP_WINDOWING_HSUM(Flags::enableDesktopWindowingHsum, true),
+ ENABLE_DESKTOP_WINDOWING_MODALS_POLICY(Flags::enableDesktopWindowingModalsPolicy, true),
+ ENABLE_DESKTOP_WINDOWING_MODE(Flags::enableDesktopWindowingMode, true),
+ ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
+ Flags::enableDesktopWindowingMultiInstanceFeatures, true),
+ ENABLE_DESKTOP_WINDOWING_PERSISTENCE(Flags::enableDesktopWindowingPersistence, false),
+ ENABLE_DESKTOP_WINDOWING_QUICK_SWITCH(Flags::enableDesktopWindowingQuickSwitch, true),
+ ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS(Flags::enableDesktopWindowingSizeConstraints, true),
+ ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS(Flags::enableDesktopWindowingTaskbarRunningApps,
+ true),
+ ENABLE_DESKTOP_WINDOWING_TASK_LIMIT(Flags::enableDesktopWindowingTaskLimit, true),
+ ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY(Flags::enableDesktopWindowingWallpaperActivity,
+ true),
+ ENABLE_HANDLE_INPUT_FIX(Flags::enableHandleInputFix, true),
+ ENABLE_HOLD_TO_DRAG_APP_HANDLE(Flags::enableHoldToDragAppHandle, true),
ENABLE_MINIMIZE_BUTTON(Flags::enableMinimizeButton, true),
ENABLE_RESIZING_METRICS(Flags::enableResizingMetrics, true),
ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS(Flags::enableTaskResizingKeyboardShortcuts, true),
- ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER(
- Flags::enableDesktopWallpaperActivityForSystemUser, true),
- ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(
- Flags::enableTopVisibleRootTaskPerUserTracking, true),
- ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
- Flags::enableDesktopRecentsTransitionsCornersBugfix, false),
- ENABLE_DESKTOP_SYSTEM_DIALOGS_TRANSITIONS(Flags::enableDesktopSystemDialogsTransitions, true),
- ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES(
- Flags::enableDesktopWindowingMultiInstanceFeatures, true);
+ ENABLE_TASK_STACK_OBSERVER_IN_SHELL(Flags::enableTaskStackObserverInShell, true),
+ ENABLE_THEMED_APP_HEADERS(Flags::enableThemedAppHeaders, true),
+ ENABLE_TILE_RESIZING(Flags::enableTileResizing, true),
+ ENABLE_TOP_VISIBLE_ROOT_TASK_PER_USER_TRACKING(Flags::enableTopVisibleRootTaskPerUserTracking,
+ true),
+ ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS(Flags::enableWindowingDynamicInitialBounds, true),
+ ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true),
+ ENABLE_WINDOWING_SCALED_RESIZING(Flags::enableWindowingScaledResizing, true),
+ ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS(
+ Flags::enableWindowingTransitionHandlersObservers, false),
+ INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC(
+ Flags::includeTopTransparentFullscreenTaskInDesktopHeuristic, true)
+ // go/keep-sorted end
+ ;
/**
* Flag class, to be used in case the enum cannot be used because the flag is not accessible.
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index d20b06738f8c..ed22ec73aac8 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -513,6 +513,13 @@ flag {
}
flag {
+ name: "enable_taskbar_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables connected displays in taskbar."
+ bug: "393398093"
+}
+
+flag {
name: "enable_bug_fixes_for_secondary_display"
namespace: "lse_desktop_experience"
description: "Bugfixes / papercuts to bring Desktop Windowing to secondary displays."
@@ -610,3 +617,17 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_desktop_taskbar_on_freeform_displays"
+ namespace: "lse_desktop_experience"
+ description: "Forces pinned taskbar with desktop tasks on freeform displays"
+ bug: "390665752"
+}
+
+flag {
+ name: "enable_presentation_for_connected_displays"
+ namespace: "lse_desktop_experience"
+ description: "Enables full support of presentation API for connected displays."
+ bug: "378503083"
+}
diff --git a/core/java/com/android/internal/notification/NotificationChannelGroupsHelper.java b/core/java/com/android/internal/notification/NotificationChannelGroupsHelper.java
new file mode 100644
index 000000000000..2bfb19f10807
--- /dev/null
+++ b/core/java/com/android/internal/notification/NotificationChannelGroupsHelper.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.notification;
+
+import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.INotificationManager;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.service.notification.Flags;
+import android.util.ArrayMap;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * NotificationChannelGroupHelper contains helper methods for associating channels with the groups
+ * they belong to, matching by ID.
+ */
+public class NotificationChannelGroupsHelper {
+ /**
+ * Set of parameters passed into
+ * {@link NotificationChannelGroupsHelper#getGroupsWithChannels(Collection, Map, Params)}.
+ *
+ * @param includeDeleted Whether to include deleted channels.
+ * @param includeNonGrouped Whether to include channels that are not associated with a group.
+ * @param includeEmpty Whether to include groups containing no channels.
+ * @param includeAllBlockedWithFilter Whether to include channels that are blocked from
+ * sending notifications along with channels specified by
+ * the filter. This setting only takes effect when
+ * channelFilter is not {@code null}, and if true will
+ * include all blocked channels in the output (regardless
+ * of whether they are included in the filter).
+ * @param channelFilter If non-null, a specific set of channels to include. If a channel
+ * matching this filter is blocked, it will still be included even
+ * if includeAllBlockedWithFilter=false.
+ */
+ public record Params(
+ boolean includeDeleted,
+ boolean includeNonGrouped,
+ boolean includeEmpty,
+ boolean includeAllBlockedWithFilter,
+ Set<String> channelFilter
+ ) {
+ /**
+ * Default set of parameters used to specify the behavior of
+ * {@link INotificationManager#getNotificationChannelGroups(String)}. This will include
+ * output for all groups, including those without channels, but not any ungrouped channels.
+ */
+ public static Params forAllGroups() {
+ return new Params(
+ /* includeDeleted= */ false,
+ /* includeNonGrouped= */ false,
+ /* includeEmpty= */ true,
+ /* includeAllBlockedWithFilter= */ true,
+ /* channelFilter= */ null);
+ }
+
+ /**
+ * Parameters to get groups for all channels, including those not associated with any groups
+ * and optionally including deleted channels as well. Channels not associated with a group
+ * are returned inside a group with id {@code null}.
+ *
+ * @param includeDeleted Whether to include deleted channels.
+ */
+ public static Params forAllChannels(boolean includeDeleted) {
+ return new Params(
+ includeDeleted,
+ /* includeNonGrouped= */ true,
+ /* includeEmpty= */ false,
+ /* includeAllBlockedWithFilter= */ true,
+ /* channelFilter= */ null);
+ }
+
+ /**
+ * Parameters to collect groups only for channels specified by the channel filter, as well
+ * as any blocked channels (independent of whether they exist in the filter).
+ * @param channelFilter Specific set of channels to return.
+ */
+ public static Params onlySpecifiedOrBlockedChannels(Set<String> channelFilter) {
+ return new Params(
+ /* includeDeleted= */ false,
+ /* includeNonGrouped= */ true,
+ /* includeEmpty= */ false,
+ /* includeAllBlockedWithFilter= */ true,
+ channelFilter);
+ }
+ }
+
+ /**
+ * Retrieve the {@link NotificationChannelGroup} object specified by the given groupId, if it
+ * exists, with the list of channels filled in from the provided available channels.
+ *
+ * @param groupId The ID of the group to return.
+ * @param allChannels A list of all channels associated with the package.
+ * @param allGroups A map of group ID -> NotificationChannelGroup objects.
+ */
+ public static @Nullable NotificationChannelGroup getGroupWithChannels(@NonNull String groupId,
+ @NonNull Collection<NotificationChannel> allChannels,
+ @NonNull Map<String, NotificationChannelGroup> allGroups,
+ boolean includeDeleted) {
+ NotificationChannelGroup group = null;
+ if (allGroups.containsKey(groupId)) {
+ group = allGroups.get(groupId).clone();
+ group.setChannels(new ArrayList<>());
+ for (NotificationChannel nc : allChannels) {
+ if (includeDeleted || !nc.isDeleted()) {
+ if (groupId.equals(nc.getGroup())) {
+ group.addChannel(nc);
+ }
+ }
+ }
+ }
+ return group;
+ }
+
+ /**
+ * Returns a list of groups with their associated channels filled in.
+ *
+ * @param allChannels All available channels that may be associated with these groups.
+ * @param allGroups Map of group ID -> {@link NotificationChannelGroup} objects.
+ * @param params Params indicating which channels and which groups to include.
+ */
+ public static @NonNull List<NotificationChannelGroup> getGroupsWithChannels(
+ @NonNull Collection<NotificationChannel> allChannels,
+ @NonNull Map<String, NotificationChannelGroup> allGroups,
+ Params params) {
+ Map<String, NotificationChannelGroup> outputGroups = new ArrayMap<>();
+ NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
+ for (NotificationChannel nc : allChannels) {
+ boolean includeChannel = (params.includeDeleted || !nc.isDeleted())
+ && (params.channelFilter == null
+ || (params.includeAllBlockedWithFilter
+ && nc.getImportance() == IMPORTANCE_NONE)
+ || params.channelFilter.contains(nc.getId()))
+ && (!Flags.notificationClassification()
+ || !SYSTEM_RESERVED_IDS.contains(nc.getId()));
+ if (includeChannel) {
+ if (nc.getGroup() != null) {
+ if (allGroups.get(nc.getGroup()) != null) {
+ NotificationChannelGroup ncg = outputGroups.get(nc.getGroup());
+ if (ncg == null) {
+ ncg = allGroups.get(nc.getGroup()).clone();
+ ncg.setChannels(new ArrayList<>());
+ outputGroups.put(nc.getGroup(), ncg);
+ }
+ ncg.addChannel(nc);
+ }
+ } else {
+ nonGrouped.addChannel(nc);
+ }
+ }
+ }
+ if (params.includeNonGrouped && nonGrouped.getChannels().size() > 0) {
+ outputGroups.put(null, nonGrouped);
+ }
+ if (params.includeEmpty) {
+ for (NotificationChannelGroup group : allGroups.values()) {
+ if (!outputGroups.containsKey(group.getId())) {
+ outputGroups.put(group.getId(), group);
+ }
+ }
+ }
+ return new ArrayList<>(outputGroups.values());
+ }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 3afe27ea591f..a2f4ca2c1b06 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -280,6 +280,7 @@ cc_library_shared_for_libandroid_runtime {
],
static_libs: [
+ "android.os.flags-aconfig-cc",
"libasync_safe",
"libbinderthreadstateutils",
"libdmabufinfo",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index aea1734918d6..5c0b72013a06 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,6 +22,7 @@
#include <android-base/parsebool.h>
#include <android-base/properties.h>
#include <android/graphics/jni_runtime.h>
+#include <android_os.h>
#include <android_runtime/AndroidRuntime.h>
#include <assert.h>
#include <binder/IBinder.h>
@@ -893,9 +894,13 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote, bool p
madviseWillNeedFileSizeOdex,
"-XMadviseWillNeedOdexFileSize:");
- parseRuntimeOption("dalvik.vm.madvise.artfile.size",
- madviseWillNeedFileSizeArt,
- "-XMadviseWillNeedArtFileSize:");
+ // Historically, dalvik.vm.madvise.artfile.size was set to UINT_MAX by default. With the
+ // disable_madvise_art_default flag rollout, we use this default only when the flag is disabled.
+ // TODO(b/382110550): Remove this property/flag entirely after validating and ramping.
+ const char* madvise_artfile_size_default =
+ android::os::disable_madvise_artfile_default() ? "" : "4294967295";
+ parseRuntimeOption("dalvik.vm.madvise.artfile.size", madviseWillNeedFileSizeArt,
+ "-XMadviseWillNeedArtFileSize:", madvise_artfile_size_default);
/*
* Profile related options.
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 407790c89202..a673ad7dfb34 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -399,6 +399,7 @@ message ActivityRecordProto {
optional bool should_override_force_resize_app = 44;
optional bool should_enable_user_aspect_ratio_settings = 45;
optional bool is_user_fullscreen_override_enabled = 46;
+ optional int64 request_open_in_browser_education_timestamp = 47;
}
/* represents WindowToken */
diff --git a/core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml b/core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml
new file mode 100644
index 000000000000..6d0c2653e5f2
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_button_rounded_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/materialColorSurfaceContainer" />
+ <corners android:radius="24dp" />
+</shape>
diff --git a/core/res/res/drawable/accessibility_autoclick_left_click.xml b/core/res/res/drawable/accessibility_autoclick_left_click.xml
new file mode 100644
index 000000000000..64c8efbe24b3
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_left_click.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="18"
+ android:viewportHeight="18">
+ <path
+ android:pathData="M5.7 12C4.1 11.9167 2.75 11.3 1.65 10.15C0.55 9 0 7.61667 0 6C0 4.33333 0.583333 2.91667 1.75 1.75C2.91667 0.583332 4.33333 -0.00000143051 6 -0.00000143051C7.61667 -0.00000143051 9 0.549999 10.15 1.65C11.3 2.75 11.9167 4.1 12 5.7L9.9 5.075C9.68333 4.175 9.21667 3.44167 8.5 2.875C7.78333 2.29167 6.95 2 6 2C4.9 2 3.95833 2.39167 3.175 3.175C2.39167 3.95833 2 4.9 2 6C2 6.95 2.28333 7.78333 2.85 8.5C3.43333 9.21667 4.175 9.68333 5.075 9.9L5.7 12ZM14.525 16.5L10.25 12.225L9 16L6 6L16 9L12.225 10.25L16.5 14.525L14.525 16.5Z"
+ android:fillColor="@color/materialColorPrimary" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_pause.xml b/core/res/res/drawable/accessibility_autoclick_pause.xml
new file mode 100644
index 000000000000..5251b2afed0d
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_pause.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+
+ <path
+ android:fillColor="@color/materialColorPrimary"
+ android:pathData="M6,19h4V5H6v14zm8,-14v14h4V5h-4z" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_position.xml b/core/res/res/drawable/accessibility_autoclick_position.xml
new file mode 100644
index 000000000000..8c98235e7d99
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_position.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="21dp"
+ android:height="17dp"
+ android:viewportWidth="21"
+ android:viewportHeight="17">
+ <path
+ android:pathData="M0.400024 2.99961C0.400024 1.67413 1.47454 0.599609 2.80002 0.599609H17.2C18.5255 0.599609 19.6 1.67413 19.6 2.99961V14.9996C19.6 16.3251 18.5255 17.3996 17.2 17.3996H2.80002C1.47454 17.3996 0.400024 16.3251 0.400024 14.9996V2.99961ZM2.80002 2.99961H17.2V14.9996H2.80002V2.99961ZM10.6 10.1996C9.60591 10.1996 8.80002 11.0055 8.80002 11.9996C8.80002 12.9937 9.60591 13.7996 10.6 13.7996H14.2C15.1941 13.7996 16 12.9937 16 11.9996C16 11.0055 15.1941 10.1996 14.2 10.1996H10.6Z"
+ android:fillType="evenOdd"
+ android:fillColor="@color/materialColorPrimary" />
+</vector>
diff --git a/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml b/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml
new file mode 100644
index 000000000000..e367ba55ae7d
--- /dev/null
+++ b/core/res/res/drawable/accessibility_autoclick_type_panel_rounded_background.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/materialColorSurface" />
+ <corners android:radius="40dp" />
+</shape>
diff --git a/core/res/res/layout/accessibility_autoclick_type_panel.xml b/core/res/res/layout/accessibility_autoclick_type_panel.xml
new file mode 100644
index 000000000000..9aa47cc8d68b
--- /dev/null
+++ b/core/res/res/layout/accessibility_autoclick_type_panel.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2025, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/accessibility_autoclick_type_panel"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:background="@drawable/accessibility_autoclick_type_panel_rounded_background"
+ android:orientation="vertical"
+ android:padding="16dp">
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="horizontal">
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_left_click_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_left_click_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_left_click"
+ android:src="@drawable/accessibility_autoclick_left_click" />
+ </LinearLayout>
+
+ <View
+ android:layout_width="@dimen/accessibility_autoclick_type_panel_divider_width"
+ android:layout_height="@dimen/accessibility_autoclick_type_panel_divider_height"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing"
+ android:background="@color/materialColorSurfaceContainer" />
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_pause_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle"
+ android:layout_marginEnd="@dimen/accessibility_autoclick_type_panel_button_spacing">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_pause_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_pause"
+ android:src="@drawable/accessibility_autoclick_pause" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/accessibility_autoclick_position_layout"
+ style="@style/AccessibilityAutoclickPanelButtonLayoutStyle">
+
+ <ImageButton
+ android:id="@+id/accessibility_autoclick_position_button"
+ style="@style/AccessibilityAutoclickPanelImageButtonStyle"
+ android:contentDescription="@string/accessibility_autoclick_position"
+ android:src="@drawable/accessibility_autoclick_position" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/core/res/res/layout/notification_2025_conversation_header.xml b/core/res/res/layout/notification_2025_conversation_header.xml
index 1bde17358825..75bd244cbbf4 100644
--- a/core/res/res/layout/notification_2025_conversation_header.xml
+++ b/core/res/res/layout/notification_2025_conversation_header.xml
@@ -29,7 +29,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
- android:textSize="16sp"
+ android:textSize="@dimen/notification_2025_title_text_size"
android:singleLine="true"
android:layout_weight="1"
/>
diff --git a/core/res/res/layout/notification_2025_template_collapsed_base.xml b/core/res/res/layout/notification_2025_template_collapsed_base.xml
index d29b7af9e24e..054583297d37 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_base.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_base.xml
@@ -102,6 +102,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_media.xml b/core/res/res/layout/notification_2025_template_collapsed_media.xml
index 5beab508aecf..9959b666b3bf 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_media.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_media.xml
@@ -104,6 +104,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
index d7c3263904d4..85ca124de8ff 100644
--- a/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_collapsed_messaging.xml
@@ -130,6 +130,7 @@
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
/>
<include layout="@layout/notification_2025_top_line_views" />
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
new file mode 100644
index 000000000000..11fc48668ad7
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_base.xml
@@ -0,0 +1,89 @@
+<?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
+ -->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ android:tag="compactHUN"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no">
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ />
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
+ >
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
+ />
+ <include layout="@layout/notification_2025_top_line_views" />
+ </NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+ </FrameLayout>
+ </LinearLayout>
+</FrameLayout>
diff --git a/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
new file mode 100644
index 000000000000..dd70087f7785
--- /dev/null
+++ b/core/res/res/layout/notification_2025_template_compact_heads_up_messaging.xml
@@ -0,0 +1,116 @@
+<?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
+ -->
+
+<com.android.internal.widget.CompactMessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/status_bar_latest_event_content"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_header_height"
+ android:clipChildren="false"
+ android:tag="compactMessagingHUN"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ android:importantForAccessibility="no">
+ <com.android.internal.widget.NotificationRowIconView
+ android:id="@+id/icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:padding="@dimen/notification_icon_circle_padding"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ />
+ <com.android.internal.widget.CachingIconView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_icon_circle_size"
+ android:layout_height="@dimen/notification_icon_circle_size"
+ android:layout_gravity="center_vertical|start"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:background="@drawable/notification_icon_circle"
+ android:clipToOutline="true"
+ android:maxDrawableWidth="@dimen/notification_icon_circle_size"
+ android:maxDrawableHeight="@dimen/notification_icon_circle_size"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no"
+ />
+ <ViewStub
+ android:layout="@layout/conversation_face_pile_layout"
+ android:layout_gravity="center_vertical|start"
+ android:layout_width="@dimen/conversation_compact_face_pile_size"
+ android:layout_height="@dimen/conversation_compact_face_pile_size"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:id="@+id/conversation_face_pile"
+ />
+ <FrameLayout
+ android:id="@+id/alternate_expand_target"
+ android:layout_width="@dimen/notification_content_margin_start"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:importantForAccessibility="no"
+ android:focusable="false"
+ />
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginStart="@dimen/notification_content_margin_start"
+ android:orientation="horizontal"
+ >
+ <NotificationTopLineView
+ android:id="@+id/notification_top_line"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_centerVertical="true"
+ android:layout_weight="1"
+ android:clipChildren="false"
+ android:gravity="center_vertical"
+ android:theme="@style/Theme.DeviceDefault.Notification"
+ >
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/notification_header_separating_margin"
+ android:ellipsize="end"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAlignment="viewStart"
+ android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@dimen/notification_2025_title_text_size"
+ />
+ <include layout="@layout/notification_2025_top_line_views" />
+ </NotificationTopLineView>
+ <FrameLayout
+ android:id="@+id/reply_action_container"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_action_list_height"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" />
+ <FrameLayout
+ android:id="@+id/expand_button_touch_container"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/notification_content_margin_end"
+ >
+ <include layout="@layout/notification_2025_expand_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|end"
+ />
+ </FrameLayout>
+ </LinearLayout>
+</com.android.internal.widget.CompactMessagingLayout>
diff --git a/core/res/res/layout/notification_2025_template_expanded_messaging.xml b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
index 7f5a36b5f865..177706c6d58d 100644
--- a/core/res/res/layout/notification_2025_template_expanded_messaging.xml
+++ b/core/res/res/layout/notification_2025_template_expanded_messaging.xml
@@ -36,14 +36,13 @@
android:clipChildren="false"
android:orientation="vertical">
- <!-- Note: the top margin is being set in code based on the estimated space needed for
- the header text. -->
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
+ android:layout_marginTop="@dimen/notification_2025_header_height"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
android:clipChildren="false"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d6b8704a978b..484e8ef1e049 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -577,6 +577,9 @@
<dimen name="notification_text_size">14sp</dimen>
<!-- Size of notification text titles (see TextAppearance.StatusBar.EventContent.Title) -->
<dimen name="notification_title_text_size">14sp</dimen>
+ <!-- Size of notification text titles, 2025 redesign version (see TextAppearance.StatusBar.EventContent.Title) -->
+ <!-- TODO: b/378660052 - When inlining the redesign flag, this should be updated directly in TextAppearance.DeviceDefault.Notification.Title -->
+ <dimen name="notification_2025_title_text_size">16sp</dimen>
<!-- Size of big notification text titles (see TextAppearance.StatusBar.EventContent.BigTitle) -->
<dimen name="notification_big_title_text_size">16sp</dimen>
<!-- Size of smaller notification text (see TextAppearance.StatusBar.EventContent.Line2, Info, Time) -->
@@ -711,6 +714,18 @@
<!-- The minimum window size of the accessibility window magnifier -->
<dimen name="accessibility_window_magnifier_min_size">122dp</dimen>
+ <!-- The accessibility autoclick panel button spacing -->
+ <dimen name="accessibility_autoclick_type_panel_button_spacing">12dp</dimen>
+
+ <!-- The accessibility autoclick panel button width and height -->
+ <dimen name="accessibility_autoclick_type_panel_button_size">36dp</dimen>
+
+ <!-- The accessibility autoclick panel divider width -->
+ <dimen name="accessibility_autoclick_type_panel_divider_width">1dp</dimen>
+
+ <!-- The accessibility autoclick panel divider height -->
+ <dimen name="accessibility_autoclick_type_panel_divider_height">24dp</dimen>
+
<!-- Margin around the various security views -->
<dimen name="keyguard_muliuser_selector_margin">8dp</dimen>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fa4c21de682e..9399e2b9824e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6149,6 +6149,16 @@
<!-- Label for Dpad center action [CHAR LIMIT=NONE] -->
<string name="accessibility_system_action_dpad_center_label">Dpad Center</string>
+ <!-- Accessibility autoclick related strings -->
+ <!-- Label for autoclick type settings panel [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_type_settings_panel_title">Autoclick type settings panel</string>
+ <!-- Label for autoclick left click button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_left_click">Left click</string>
+ <!-- Label for autoclick pause button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_pause">Pause</string>
+ <!-- Label for autoclick position button [CHAR LIMIT=NONE] -->
+ <string name="accessibility_autoclick_position">Position</string>
+
<!-- Text to tell the user that a package has been forced by themselves in the RESTRICTED bucket. [CHAR LIMIT=NONE] -->
<string name="as_app_forced_to_restricted_bucket">
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 579dc91d2ca1..ee1edda838fd 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -1745,6 +1745,22 @@ please see styles_device_defaults.xml.
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
+ <style name="AccessibilityAutoclickPanelButtonLayoutStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:background">@drawable/accessibility_autoclick_button_rounded_background</item>
+ <item name="android:layout_width">@dimen/accessibility_autoclick_type_panel_button_size</item>
+ <item name="android:layout_height">@dimen/accessibility_autoclick_type_panel_button_size</item>
+ </style>
+
+ <style name="AccessibilityAutoclickPanelImageButtonStyle">
+ <item name="android:gravity">center</item>
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:scaleType">center</item>
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:tint">@color/materialColorPrimary</item>
+ </style>
+
<!--
TODO(b/309578419): Make activities go edge-to-edge properly and then remove this.
-->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index aca9d30a2607..653996afbc01 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -574,6 +574,7 @@
<java-symbol type="dimen" name="notification_text_size" />
<java-symbol type="dimen" name="notification_title_text_size" />
<java-symbol type="dimen" name="notification_subtext_size" />
+ <java-symbol type="dimen" name="notification_2025_title_text_size" />
<java-symbol type="dimen" name="notification_top_pad" />
<java-symbol type="dimen" name="notification_top_pad_narrow" />
<java-symbol type="dimen" name="notification_top_pad_large_text" />
@@ -2411,6 +2412,8 @@
<java-symbol type="layout" name="notification_2025_template_collapsed_base" />
<java-symbol type="layout" name="notification_2025_template_expanded_base" />
<java-symbol type="layout" name="notification_2025_template_heads_up_base" />
+ <java-symbol type="layout" name="notification_2025_template_compact_heads_up_base" />
+ <java-symbol type="layout" name="notification_2025_template_compact_heads_up_messaging" />
<java-symbol type="layout" name="notification_2025_template_header" />
<java-symbol type="layout" name="notification_2025_template_collapsed_messaging" />
<java-symbol type="layout" name="notification_2025_template_collapsed_media" />
@@ -5604,6 +5607,24 @@
<java-symbol type="bool" name="config_enable_a11y_fullscreen_magnification_overscroll_handler" />
<java-symbol type="dimen" name="accessibility_fullscreen_magnification_gesture_edge_slop" />
+ <!-- Accessibility autoclick related -->
+ <java-symbol type="layout" name="accessibility_autoclick_type_panel" />
+ <java-symbol type="string" name="accessibility_autoclick_type_settings_panel_title" />
+ <java-symbol type="string" name="accessibility_autoclick_left_click" />
+ <java-symbol type="string" name="accessibility_autoclick_pause" />
+ <java-symbol type="string" name="accessibility_autoclick_position" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_button_spacing" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_button_size" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_divider_height" />
+ <java-symbol type="dimen" name="accessibility_autoclick_type_panel_divider_width" />
+ <java-symbol type="id" name="accessibility_autoclick_type_panel" />
+ <java-symbol type="id" name="accessibility_autoclick_left_click_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_left_click_button" />
+ <java-symbol type="id" name="accessibility_autoclick_pause_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_pause_button" />
+ <java-symbol type="id" name="accessibility_autoclick_position_layout" />
+ <java-symbol type="id" name="accessibility_autoclick_position_button" />
+
<!-- For HapticFeedbackConstants configurability defined at HapticFeedbackCustomization -->
<java-symbol type="string" name="config_hapticFeedbackCustomizationFile" />
<java-symbol type="xml" name="haptic_feedback_customization" />
diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
index dc2f0a69375d..8fa510381060 100644
--- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
+++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java
@@ -307,10 +307,8 @@ public class DisplayManagerGlobalTest {
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED,
mDisplayManagerGlobal
.mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0));
- assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED,
- mDisplayManagerGlobal
- .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED,
- 0));
+ assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal
+ .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0));
assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED,
mDisplayManagerGlobal.mapFiltersToInternalEventFlag(
DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0));
diff --git a/core/tests/coretests/src/android/os/PerfettoTraceTest.java b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
index 0b5a44665d2b..91efacf0dcc3 100644
--- a/core/tests/coretests/src/android/os/PerfettoTraceTest.java
+++ b/core/tests/coretests/src/android/os/PerfettoTraceTest.java
@@ -59,6 +59,8 @@ import perfetto.protos.TrackEventOuterClass.TrackEvent;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* This class is used to test the native tracing support. Run this test
@@ -77,6 +79,8 @@ public class PerfettoTraceTest {
private static final String BAR = "bar";
private static final Category FOO_CATEGORY = new Category(FOO);
+ private static final int MESSAGE = 1234567;
+ private static final int MESSAGE_COUNT = 3;
private final Set<String> mCategoryNames = new ArraySet<>();
private final Set<String> mEventNames = new ArraySet<>();
@@ -592,6 +596,89 @@ public class PerfettoTraceTest {
assertThat(mDebugAnnotationNames).doesNotContain("before");
}
+ @Test
+ @RequiresFlagsEnabled(android.os.Flags.FLAG_PERFETTO_SDK_TRACING_V2)
+ public void testMessageQueue() throws Exception {
+ TraceConfig traceConfig = getTraceConfig("mq");
+
+ PerfettoTrace.MQ_CATEGORY.register();
+ final HandlerThread thread = new HandlerThread("test");
+ thread.start();
+ final Handler handler = thread.getThreadHandler();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ PerfettoTrace.Session session = new PerfettoTrace.Session(true,
+ getTraceConfig("mq").toByteArray());
+
+ handler.sendEmptyMessage(MESSAGE);
+ handler.sendEmptyMessageDelayed(MESSAGE, 10);
+ handler.sendEmptyMessage(MESSAGE);
+ handler.postDelayed(() -> {
+ latch.countDown();
+ }, 10);
+ assertThat(latch.await(100, TimeUnit.MILLISECONDS)).isTrue();
+
+ byte[] traceBytes = session.close();
+
+ Trace trace = Trace.parseFrom(traceBytes);
+
+ boolean hasTrackEvent = false;
+ int instantCount = 0;
+ int counterCount = 0;
+ int beginCount = 0;
+ int endCount = 0;
+
+ Set<Long> flowIds = new ArraySet<>();
+ for (TracePacket packet: trace.getPacketList()) {
+ TrackEvent event;
+ if (packet.hasTrackEvent()) {
+ hasTrackEvent = true;
+ event = packet.getTrackEvent();
+ } else {
+ continue;
+ }
+
+ List<DebugAnnotation> annotations = event.getDebugAnnotationsList();
+ switch (event.getType()) {
+ case TrackEvent.Type.TYPE_INSTANT:
+ if (annotations.get(2).getIntValue() == MESSAGE) {
+ instantCount++;
+ assertThat(annotations.get(0).getStringValue()).isEqualTo("test");
+ assertThat(event.getFlowIdsCount()).isEqualTo(1);
+ flowIds.addAll(event.getFlowIdsList());
+ }
+ break;
+ case TrackEvent.Type.TYPE_COUNTER:
+ counterCount++;
+ break;
+ case TrackEvent.Type.TYPE_SLICE_BEGIN:
+ annotations = event.getDebugAnnotationsList();
+ if (flowIds.containsAll(event.getTerminatingFlowIdsList())) {
+ beginCount++;
+ assertThat(event.getTerminatingFlowIdsCount()).isEqualTo(1);
+ }
+ break;
+ case TrackEvent.Type.TYPE_SLICE_END:
+ endCount++;
+ break;
+ default:
+ break;
+ }
+ collectInternedData(packet);
+ }
+
+ assertThat(hasTrackEvent).isTrue();
+ assertThat(mCategoryNames).contains("mq");
+ assertThat(mEventNames).contains("message_queue_send");
+ assertThat(mEventNames).contains("message_queue_receive");
+ assertThat(mDebugAnnotationNames).contains("what");
+ assertThat(mDebugAnnotationNames).contains("delay");
+ assertThat(instantCount).isEqualTo(MESSAGE_COUNT);
+ assertThat(beginCount).isEqualTo(MESSAGE_COUNT);
+ assertThat(endCount).isAtLeast(MESSAGE_COUNT);
+ assertThat(counterCount).isAtLeast(MESSAGE_COUNT);
+ }
+
private TrackEvent getTrackEvent(Trace trace, int idx) {
int curIdx = 0;
for (TracePacket packet: trace.getPacketList()) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index c40137f1bd34..a289df0441e5 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
import static android.util.SequenceUtils.getInitSeq;
import static android.view.HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
@@ -67,8 +69,10 @@ import static org.junit.Assume.assumeTrue;
import android.annotation.NonNull;
import android.app.Instrumentation;
import android.app.UiModeManager;
+import android.app.UiModeManager.ForceInvertType;
import android.content.Context;
import android.graphics.ForceDarkType;
+import android.graphics.ForceDarkType.ForceDarkTypeDef;
import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
@@ -93,9 +97,12 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.TestUtils;
import com.android.cts.input.BlockingQueueEventVerifier;
import com.android.window.flags.Flags;
+import com.google.common.truth.Expect;
+
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.AfterClass;
@@ -124,6 +131,8 @@ public class ViewRootImplTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Rule
+ public final Expect mExpect = Expect.create();
private ViewRootImpl mViewRootImpl;
private View mView;
@@ -1507,49 +1516,34 @@ public class ViewRootImplTest {
}
@Test
- public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 0
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
- }
-
- @Test
- public void forceInvertOnDarkThemeOff_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
- ShellIdentityUtils.invokeWithShellPermissions(() -> {
- Settings.Secure.putInt(
- sContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
- /* value= */ 1
- );
- var uiModeManager = sContext.getSystemService(UiModeManager.class);
- uiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
- });
-
- sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
-
- assertThat(mViewRootImpl.determineForceDarkType())
- .isEqualTo(ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ @RequiresFlagsEnabled(FLAG_FORCE_INVERT_COLOR)
+ public void updateConfiguration_returnsExpectedForceDarkMode() {
+ waitForSystemNightModeActivated(true);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_DARK, ForceDarkType.FORCE_INVERT_COLOR_DARK);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+
+ waitForSystemNightModeActivated(false);
+
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ true, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ true,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
+ verifyForceDarkType(/* isAppInNightMode= */ false, /* isForceInvertEnabled= */ false,
+ UiModeManager.FORCE_INVERT_TYPE_OFF, ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOff_forceDarkModeDisabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1562,15 +1556,14 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.NONE);
}
@Test
+ @EnableFlags(FLAG_FORCE_INVERT_COLOR)
public void forceInvertOffForceDarkOn_forceDarkModeEnabled() {
- mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
Settings.Secure.putInt(
sContext.getContentResolver(),
@@ -1582,8 +1575,7 @@ public class ViewRootImplTest {
});
sInstrumentation.runOnMainSync(() ->
- mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId())
- );
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
assertThat(mViewRootImpl.determineForceDarkType()).isEqualTo(ForceDarkType.FORCE_DARK);
}
@@ -1790,4 +1782,39 @@ public class ViewRootImplTest {
() -> view.getViewTreeObserver().removeOnDrawListener(listener));
}
}
+
+ private void waitForSystemNightModeActivated(boolean active) {
+ ShellIdentityUtils.invokeWithShellPermissions(() ->
+ sInstrumentation.runOnMainSync(() -> {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ uiModeManager.setNightModeActivated(active);
+ }));
+ sInstrumentation.waitForIdleSync();
+ }
+
+ private void verifyForceDarkType(boolean isAppInNightMode, boolean isForceInvertEnabled,
+ @ForceInvertType int expectedForceInvertType,
+ @ForceDarkTypeDef int expectedForceDarkType) {
+ var uiModeManager = sContext.getSystemService(UiModeManager.class);
+ ShellIdentityUtils.invokeWithShellPermissions(() -> {
+ uiModeManager.setApplicationNightMode(
+ isAppInNightMode ? MODE_NIGHT_YES : MODE_NIGHT_NO);
+ Settings.Secure.putInt(
+ sContext.getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ isForceInvertEnabled ? 1 : 0);
+ });
+
+ sInstrumentation.runOnMainSync(() ->
+ mViewRootImpl.updateConfiguration(sContext.getDisplayNoVerify().getDisplayId()));
+ try {
+ TestUtils.waitUntil("Waiting for force invert state changed",
+ () -> (uiModeManager.getForceInvertState() == expectedForceInvertType));
+ } catch (Exception e) {
+ Log.e(TAG, "Unexpected error trying to apply force invert state. " + e);
+ e.printStackTrace();
+ }
+
+ mExpect.that(mViewRootImpl.determineForceDarkType()).isEqualTo(expectedForceDarkType);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/notification/NotificationChannelGroupsHelperTest.java b/core/tests/coretests/src/com/android/internal/notification/NotificationChannelGroupsHelperTest.java
new file mode 100644
index 000000000000..26e96ea79e4b
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/NotificationChannelGroupsHelperTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.notification;
+
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+
+import static com.android.internal.notification.NotificationChannelGroupsHelper.getGroupWithChannels;
+import static com.android.internal.notification.NotificationChannelGroupsHelper.getGroupsWithChannels;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.notification.NotificationChannelGroupsHelper.Params;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RunWith(AndroidJUnit4.class)
+public class NotificationChannelGroupsHelperTest {
+ private Collection<NotificationChannel> mChannels;
+ private Map<String, NotificationChannelGroup> mGroups;
+
+ @Before
+ public void setUp() {
+ // Test data setup.
+ // Channels and their corresponding groups:
+ // * "regular": a channel that is not deleted or blocked. In group A.
+ // * "blocked": blocked channel. In group A.
+ // * "deleted": deleted channel. In group A.
+ // * "adrift": regular channel. No group.
+ // * "gone": deleted channel. No group.
+ // * "alternate": regular channel. In group B.
+ // * "another blocked": blocked channel. In group B.
+ // * "another deleted": deleted channel. In group C.
+ // * Additionally, there is an empty group D.
+ mChannels = List.of(makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false),
+ makeChannel("deleted", "a", false, true),
+ makeChannel("adrift", null, false, false),
+ makeChannel("gone", null, false, true),
+ makeChannel("alternate", "b", false, false),
+ makeChannel("anotherBlocked", "b", true, false),
+ makeChannel("anotherDeleted", "c", false, true));
+
+ mGroups = Map.of("a", new NotificationChannelGroup("a", "a"),
+ "b", new NotificationChannelGroup("b", "b"),
+ "c", new NotificationChannelGroup("c", "c"),
+ "d", new NotificationChannelGroup("d", "d"));
+ }
+
+ @Test
+ public void testGetGroup_noDeleted() {
+ NotificationChannelGroup res = getGroupWithChannels("a", mChannels, mGroups, false);
+ assertThat(res).isNotNull();
+ assertThat(res.getChannels()).hasSize(2); // "regular" & "blocked"
+ assertThat(res.getChannels()).containsExactlyElementsIn(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false)));
+ }
+
+ @Test
+ public void testGetGroup_includeDeleted() {
+ NotificationChannelGroup res = getGroupWithChannels("c", mChannels, mGroups, true);
+ assertThat(res).isNotNull();
+ assertThat(res.getChannels()).hasSize(1);
+ assertThat(res.getChannels().getFirst()).isEqualTo(
+ makeChannel("anotherDeleted", "c", false, true));
+ }
+
+ @Test
+ public void testGetGroup_empty() {
+ NotificationChannelGroup res = getGroupWithChannels("d", mChannels, mGroups, true);
+ assertThat(res).isNotNull();
+ assertThat(res.getChannels()).isEmpty();
+ }
+
+ @Test
+ public void testGetGroup_emptyBecauseNoChannelMatch() {
+ NotificationChannelGroup res = getGroupWithChannels("c", mChannels, mGroups, false);
+ assertThat(res).isNotNull();
+ assertThat(res.getChannels()).isEmpty();
+ }
+
+ @Test
+ public void testGetGroup_nonexistent() {
+ NotificationChannelGroup res = getGroupWithChannels("e", mChannels, mGroups, true);
+ assertThat(res).isNull();
+ }
+
+ @Test
+ public void testGetGroups_paramsForAllGroups() {
+ // deleted=false, nongrouped=false, empty=true, blocked=true, no channel filter
+ List<NotificationChannelGroup> res = getGroupsWithChannels(mChannels, mGroups,
+ Params.forAllGroups());
+
+ NotificationChannelGroup expectedA = new NotificationChannelGroup("a", "a");
+ expectedA.setChannels(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false)));
+
+ NotificationChannelGroup expectedB = new NotificationChannelGroup("b", "b");
+ expectedB.setChannels(List.of(
+ makeChannel("alternate", "b", false, false),
+ makeChannel("anotherBlocked", "b", true, false)));
+
+ NotificationChannelGroup expectedC = new NotificationChannelGroup("c", "c");
+ expectedC.setChannels(new ArrayList<>()); // empty, no deleted
+
+ NotificationChannelGroup expectedD = new NotificationChannelGroup("d", "d");
+ expectedD.setChannels(new ArrayList<>()); // empty
+
+ assertThat(res).containsExactly(expectedA, expectedB, expectedC, expectedD);
+ }
+
+ @Test
+ public void testGetGroups_paramsForAllChannels_noDeleted() {
+ // Excluding deleted channels to means group C is not included because it's "empty"
+ List<NotificationChannelGroup> res = getGroupsWithChannels(mChannels, mGroups,
+ Params.forAllChannels(false));
+
+ NotificationChannelGroup expectedA = new NotificationChannelGroup("a", "a");
+ expectedA.setChannels(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false)));
+
+ NotificationChannelGroup expectedB = new NotificationChannelGroup("b", "b");
+ expectedB.setChannels(List.of(
+ makeChannel("alternate", "b", false, false),
+ makeChannel("anotherBlocked", "b", true, false)));
+
+ NotificationChannelGroup expectedUngrouped = new NotificationChannelGroup(null, null);
+ expectedUngrouped.setChannels(List.of(
+ makeChannel("adrift", null, false, false),
+ makeChannel("gone", null, false, true)));
+
+ assertThat(res).containsExactly(expectedA, expectedB, expectedUngrouped);
+ }
+
+ @Test
+ public void testGetGroups_paramsForAllChannels_withDeleted() {
+ // This will get everything!
+ List<NotificationChannelGroup> res = getGroupsWithChannels(mChannels, mGroups,
+ Params.forAllChannels(true));
+
+ NotificationChannelGroup expectedA = new NotificationChannelGroup("a", "a");
+ expectedA.setChannels(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false),
+ makeChannel("deleted", "a", false, true)));
+
+ NotificationChannelGroup expectedB = new NotificationChannelGroup("b", "b");
+ expectedB.setChannels(List.of(
+ makeChannel("alternate", "b", false, false),
+ makeChannel("anotherBlocked", "b", true, false)));
+
+ NotificationChannelGroup expectedC = new NotificationChannelGroup("c", "c");
+ expectedC.setChannels(List.of(makeChannel("anotherDeleted", "c", false, true)));
+
+ // no D, because D is empty
+
+ NotificationChannelGroup expectedUngrouped = new NotificationChannelGroup(null, null);
+ expectedUngrouped.setChannels(List.of(makeChannel("adrift", null, false, false)));
+
+ assertThat(res).containsExactly(expectedA, expectedB, expectedC, expectedUngrouped);
+ }
+
+ @Test
+ public void testGetGroups_onlySpecifiedOrBlocked() {
+ Set<String> filter = Set.of("regular", "blocked", "adrift", "anotherDeleted");
+
+ // also not including deleted channels to check intersection of those params
+ List<NotificationChannelGroup> res = getGroupsWithChannels(mChannels, mGroups,
+ Params.onlySpecifiedOrBlockedChannels(filter));
+
+ NotificationChannelGroup expectedA = new NotificationChannelGroup("a", "a");
+ expectedA.setChannels(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false)));
+
+ // While nothing matches the filter from group B, includeBlocked=true means all blocked
+ // channels are included even if they are not in the filter.
+ NotificationChannelGroup expectedB = new NotificationChannelGroup("b", "b");
+ expectedB.setChannels(List.of(makeChannel("anotherBlocked", "b", true, false)));
+
+ NotificationChannelGroup expectedC = new NotificationChannelGroup("c", "c");
+ expectedC.setChannels(new ArrayList<>()); // deleted channel not included
+
+ NotificationChannelGroup expectedD = new NotificationChannelGroup("d", "d");
+ expectedD.setChannels(new ArrayList<>()); // empty
+
+ NotificationChannelGroup expectedUngrouped = new NotificationChannelGroup(null, null);
+ expectedUngrouped.setChannels(List.of(makeChannel("adrift", null, false, false)));
+
+ assertThat(res).containsExactly(expectedA, expectedB, expectedC, expectedD,
+ expectedUngrouped);
+ }
+
+
+ @Test
+ public void testGetGroups_noBlockedWithFilter() {
+ Set<String> filter = Set.of("regular", "blocked", "adrift");
+
+ // The includeBlocked setting only takes effect if there is a channel filter.
+ List<NotificationChannelGroup> res = getGroupsWithChannels(mChannels, mGroups,
+ new Params(true, true, true, false, filter));
+
+ // Even though includeBlocked=false, "blocked" is included because it's explicitly specified
+ // by the channel filter.
+ NotificationChannelGroup expectedA = new NotificationChannelGroup("a", "a");
+ expectedA.setChannels(List.of(
+ makeChannel("regular", "a", false, false),
+ makeChannel("blocked", "a", true, false)));
+
+ NotificationChannelGroup expectedB = new NotificationChannelGroup("b", "b");
+ expectedB.setChannels(new ArrayList<>()); // no matches; blocked channel not in filter
+
+ NotificationChannelGroup expectedC = new NotificationChannelGroup("c", "c");
+ expectedC.setChannels(new ArrayList<>()); // no matches
+
+ NotificationChannelGroup expectedD = new NotificationChannelGroup("d", "d");
+ expectedD.setChannels(new ArrayList<>()); // empty
+
+ NotificationChannelGroup expectedUngrouped = new NotificationChannelGroup(null, null);
+ expectedUngrouped.setChannels(List.of(makeChannel("adrift", null, false, false)));
+
+ assertThat(res).containsExactly(expectedA, expectedB, expectedC, expectedD,
+ expectedUngrouped);
+ }
+
+ private NotificationChannel makeChannel(String id, String groupId, boolean blocked,
+ boolean deleted) {
+ NotificationChannel c = new NotificationChannel(id, id,
+ blocked ? IMPORTANCE_NONE : IMPORTANCE_DEFAULT);
+ if (deleted) {
+ c.setDeleted(true);
+ }
+ if (groupId != null) {
+ c.setGroup(groupId);
+ }
+ return c;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/notification/OWNERS b/core/tests/coretests/src/com/android/internal/notification/OWNERS
new file mode 100644
index 000000000000..396fd1213aca
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/notification/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/notification/OWNERS
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
index 36c73e2e979e..c42ddd3412be 100644
--- a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -44,8 +44,11 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
"com.android.server.om.hosttest.update_overlay_test";
private static final String DEVICE_TEST_CLS = DEVICE_TEST_PKG + ".UpdateOverlayTest";
+ private int mCurrentUserid;
+
@Before
public void ensureNoOverlays() throws Exception {
+ mCurrentUserid = getDevice().getCurrentUser();
// Make sure we're starting with a clean slate.
for (String pkg : ALL_PACKAGES) {
assertFalse(pkg + " should not be installed", isPackageInstalled(pkg));
@@ -62,7 +65,7 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
@After
public void uninstallOverlays() throws Exception {
for (String pkg : ALL_PACKAGES) {
- uninstallPackage(pkg);
+ getDevice().uninstallPackageForUser(pkg, mCurrentUserid);
}
}
@@ -166,7 +169,7 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
installPackage("OverlayHostTests_AppOverlayV1.apk");
assertTrue(getDevice().executeShellCommand("cat /data/system/overlays.xml")
.contains(APP_OVERLAY_PACKAGE_NAME));
- uninstallPackage(APP_OVERLAY_PACKAGE_NAME);
+ getDevice().uninstallPackageForUser(APP_OVERLAY_PACKAGE_NAME, mCurrentUserid);
delay();
assertFalse(getDevice().executeShellCommand("cat /data/system/overlays.xml")
.contains(APP_OVERLAY_PACKAGE_NAME));
@@ -200,12 +203,12 @@ public class InstallOverlayTests extends BaseHostJUnit4Test {
}
private void installPackage(String pkg) throws Exception {
- super.installPackage(pkg);
+ super.installPackageAsUser(pkg, true /* grantPermission */, mCurrentUserid);
delay();
}
private void installInstantPackage(String pkg) throws Exception {
- super.installPackage(pkg, "--instant");
+ super.installPackageAsUser(pkg, true /* grantPermission */, mCurrentUserid, "--instant");
delay();
}
diff --git a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
index fd578a959e3b..95cd1c72a2af 100644
--- a/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/multivalentTests/AndroidManifest.xml
@@ -1,10 +1,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.wm.shell.multivalenttests">
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_TASKS"/>
+
<application android:debuggable="true" android:supportsRtl="true" >
<uses-library android:name="android.test.runner" />
<activity android:name="com.android.wm.shell.bubbles.bar.BubbleBarAnimationHelperTest$TestActivity"
android:exported="true"/>
+
+ <activity android:name=".bubbles.TestActivity"
+ android:allowEmbedded="true"
+ android:documentLaunchMode="always"
+ android:excludeFromRecents="true"
+ android:exported="false"
+ android:resizeableActivity="true" />
</application>
<instrumentation
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
index 09a93d501f8e..bce6c5999a75 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerBubbleBarTest.kt
@@ -35,7 +35,6 @@ import com.android.internal.statusbar.IStatusBarService
import com.android.wm.shell.Flags
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
-import com.android.wm.shell.bubbles.properties.ProdBubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayImeController
@@ -48,6 +47,7 @@ import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.shared.TransactionPool
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
@@ -288,7 +288,7 @@ class BubbleControllerBubbleBarTest {
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
mock<IWindowManager>(),
- ProdBubbleProperties,
+ BubbleResizabilityChecker()
)
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt
new file mode 100644
index 000000000000..cec67f26af92
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Context
+import android.content.Intent
+import android.content.pm.LauncherApps
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.os.Handler
+import android.os.UserHandle
+import android.os.UserManager
+import android.view.IWindowManager
+import android.view.WindowManager
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.internal.protolog.ProtoLog
+import com.android.internal.statusbar.IStatusBarService
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
+import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayImeController
+import com.android.wm.shell.common.DisplayInsetsController
+import com.android.wm.shell.common.FloatingContentCoordinator
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.common.TaskStackListenerImpl
+import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.common.TestSyncExecutor
+import com.android.wm.shell.draganddrop.DragAndDropController
+import com.android.wm.shell.recents.RecentTasksController
+import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellController
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.taskview.TaskViewRepository
+import com.android.wm.shell.taskview.TaskViewTransitions
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.unfold.UnfoldAnimationController
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+import java.util.Optional
+
+/** Tests for [BubbleControllerTest] */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class BubbleControllerTest {
+
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ private lateinit var bubbleController: BubbleController
+ private lateinit var bubblePositioner: BubblePositioner
+ private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var bubbleLogger: BubbleLogger
+ private lateinit var mainExecutor: TestShellExecutor
+ private lateinit var bgExecutor: TestShellExecutor
+ private lateinit var bubbleData: BubbleData
+ private lateinit var eduController: BubbleEducationController
+
+ @Before
+ fun setUp() {
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
+
+ uiEventLoggerFake = UiEventLoggerFake()
+ bubbleLogger = BubbleLogger(uiEventLoggerFake)
+ eduController = BubbleEducationController(context)
+
+ mainExecutor = TestShellExecutor()
+ bgExecutor = TestShellExecutor()
+
+ // Tests don't have permission to add our window to windowManager, so we mock it :(
+ val windowManager = mock<WindowManager>()
+ val realWindowManager = context.getSystemService(WindowManager::class.java)
+ // But we do want the metrics from the real one
+ whenever(windowManager.currentWindowMetrics)
+ .thenReturn(realWindowManager.currentWindowMetrics)
+
+ bubblePositioner = BubblePositioner(context, windowManager)
+ bubblePositioner.setShowingInBubbleBar(true)
+
+ bubbleData = BubbleData(
+ context, bubbleLogger, bubblePositioner, eduController,
+ mainExecutor, bgExecutor
+ )
+
+ bubbleController =
+ createBubbleController(
+ bubbleData,
+ windowManager,
+ bubbleLogger,
+ bubblePositioner,
+ mainExecutor,
+ bgExecutor,
+ )
+ bubbleController.asBubbles().setSysuiProxy(Mockito.mock(SysuiProxy::class.java))
+ // Flush so that proxy gets set
+ mainExecutor.flushAll()
+ }
+
+ @After
+ fun tearDown() {
+ getInstrumentation().waitForIdleSync()
+ }
+
+ @Test
+ fun showOrHideNotesBubble_createsNoteBubble() {
+ val intent = Intent(context, TestActivity::class.java)
+ intent.setPackage(context.packageName)
+ val user = UserHandle.of(0)
+ val expectedKey = Bubble.getNoteBubbleKeyForApp(intent.getPackage(), user)
+
+ getInstrumentation().runOnMainSync {
+ bubbleController.showOrHideNotesBubble(intent, user, mock<Icon>())
+ }
+ getInstrumentation().waitForIdleSync()
+
+ assertThat(bubbleController.hasBubbles()).isTrue()
+ assertThat(bubbleData.getAnyBubbleWithKey(expectedKey)).isNotNull()
+ assertThat(bubbleData.getAnyBubbleWithKey(expectedKey)!!.isNoteBubble).isTrue()
+ }
+
+
+ fun createBubbleController(
+ bubbleData: BubbleData,
+ windowManager: WindowManager?,
+ bubbleLogger: BubbleLogger,
+ bubblePositioner: BubblePositioner,
+ mainExecutor: TestShellExecutor,
+ bgExecutor: TestShellExecutor,
+ ): BubbleController {
+ val shellInit = ShellInit(mainExecutor)
+ val shellCommandHandler = ShellCommandHandler()
+ val shellController =
+ ShellController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ mock<DisplayInsetsController>(),
+ mainExecutor,
+ )
+ val surfaceSynchronizer = { obj: Runnable -> obj.run() }
+
+ val bubbleDataRepository =
+ BubbleDataRepository(
+ mock<LauncherApps>(),
+ mainExecutor,
+ bgExecutor,
+ BubblePersistentRepository(context),
+ )
+
+ val shellTaskOrganizer = ShellTaskOrganizer(
+ Mockito.mock<ShellInit>(ShellInit::class.java),
+ ShellCommandHandler(),
+ null,
+ Optional.empty<UnfoldAnimationController>(),
+ Optional.empty<RecentTasksController>(),
+ TestSyncExecutor()
+ )
+
+ val resizeChecker: ResizabilityChecker =
+ object : ResizabilityChecker {
+ override fun isResizableActivity(
+ intent: Intent?,
+ packageManager: PackageManager, key: String
+ ): Boolean {
+ return true
+ }
+ }
+
+ val bubbleController = BubbleController(
+ context,
+ shellInit,
+ shellCommandHandler,
+ shellController,
+ bubbleData,
+ surfaceSynchronizer,
+ FloatingContentCoordinator(),
+ bubbleDataRepository,
+ mock<IStatusBarService>(),
+ windowManager,
+ mock<DisplayInsetsController>(),
+ mock<DisplayImeController>(),
+ mock<UserManager>(),
+ mock<LauncherApps>(),
+ bubbleLogger,
+ mock<TaskStackListenerImpl>(),
+ shellTaskOrganizer,
+ bubblePositioner,
+ mock<DisplayController>(),
+ Optional.empty(),
+ mock<DragAndDropController>(),
+ mainExecutor,
+ mock<Handler>(),
+ bgExecutor,
+ mock<TaskViewRepository>(),
+ mock<TaskViewTransitions>(),
+ mock<Transitions>(),
+ SyncTransactionQueue(TransactionPool(), mainExecutor),
+ mock<IWindowManager>(),
+ resizeChecker,
+ )
+ bubbleController.setInflateSynchronously(true)
+ bubbleController.onInit()
+
+ return bubbleController
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index 1d0c5057c77f..ec1add21ebf8 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -31,6 +31,7 @@ import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner.MAX_HEIGHT
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import com.google.common.util.concurrent.MoreExecutors.directExecutor
import org.junit.Before
@@ -154,19 +155,19 @@ class BubblePositionerTest {
/** Test that the default resting position on tablet is middle right. */
@Test
- fun testGetDefaultPosition_appBubble_onTablet() {
+ fun testGetDefaultPosition_noteBubble_onTablet() {
positioner.update(defaultDeviceConfig.copy(isLargeScreen = true))
val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isNoteBubble */)
assertThat(startPosition.x).isEqualTo(allowableStackRegion.right)
assertThat(startPosition.y).isEqualTo(defaultYPosition)
}
@Test
- fun testGetRestingPosition_appBubble_onTablet_RTL() {
+ fun testGetRestingPosition_noteBubble_onTablet_RTL() {
positioner.update(defaultDeviceConfig.copy(isLargeScreen = true, isRtl = true))
val allowableStackRegion = positioner.getAllowableStackPositionRegion(1 /* bubbleCount */)
- val startPosition = positioner.getDefaultStartPosition(true /* isAppBubble */)
+ val startPosition = positioner.getDefaultStartPosition(true /* isNoteBubble */)
assertThat(startPosition.x).isEqualTo(allowableStackRegion.left)
assertThat(startPosition.y).isEqualTo(defaultYPosition)
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
index f1ba0423b422..77aee98e6f6e 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt
@@ -35,7 +35,6 @@ import com.android.internal.protolog.ProtoLog
import com.android.internal.statusbar.IStatusBarService
import com.android.launcher3.icons.BubbleIconFactory
import com.android.wm.shell.ShellTaskOrganizer
-import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayImeController
@@ -161,7 +160,7 @@ class BubbleViewInfoTaskTest {
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
mock<IWindowManager>(),
- mock<BubbleProperties>()
+ BubbleResizabilityChecker()
)
// TODO: (b/371829099) - when optional overflow is no longer flagged we can enable this
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt
index 3c013d3636e8..adcd835d72be 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/FakeBubbleExpandedViewManager.kt
@@ -38,7 +38,7 @@ class FakeBubbleExpandedViewManager(var bubbleBar: Boolean = false, var expanded
override fun dismissBubble(bubble: Bubble, reason: Int) {}
- override fun setAppBubbleTaskId(key: String, taskId: Int) {}
+ override fun setNoteBubbleTaskId(key: String, taskId: Int) {}
override fun isStackExpanded(): Boolean {
return expanded
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/TestActivity.kt
index 013158676f79..40e80d02e7b3 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/PackageConfig.aidl
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/TestActivity.kt
@@ -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.
@@ -13,10 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package com.android.wm.shell.bubbles
-package android.service.watchdog;
+import android.app.Activity
+import android.os.Bundle
+import android.widget.FrameLayout
-/**
- * @hide
- */
-parcelable PackageConfig;
+class TestActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(FrameLayout(getApplicationContext()))
+ }
+}
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 d3cfbd00c4a3..68b3d8822525 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
@@ -42,10 +42,10 @@ import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubbleOverflow
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.BubbleTaskView
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewController
import com.android.wm.shell.taskview.TaskViewTaskController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
index 7f65e22736b3..037bd227d33c 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt
@@ -39,11 +39,11 @@ import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubblePositioner
import com.android.wm.shell.bubbles.BubbleTaskView
import com.android.wm.shell.bubbles.BubbleTaskViewFactory
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.RegionSamplingProvider
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.common.TestShellExecutor
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.android.wm.shell.shared.handles.RegionSamplingHelper
import com.android.wm.shell.taskview.TaskView
import com.android.wm.shell.taskview.TaskViewController
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index a6492476176b..c022a298e972 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -43,13 +43,13 @@ import com.android.wm.shell.bubbles.BubbleDataRepository
import com.android.wm.shell.bubbles.BubbleExpandedViewManager
import com.android.wm.shell.bubbles.BubbleLogger
import com.android.wm.shell.bubbles.BubblePositioner
+import com.android.wm.shell.bubbles.BubbleResizabilityChecker
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.FakeBubbleExpandedViewManager
import com.android.wm.shell.bubbles.FakeBubbleFactory
import com.android.wm.shell.bubbles.FakeBubbleTaskViewFactory
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
-import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayImeController
@@ -200,7 +200,7 @@ class BubbleBarLayerViewTest {
mock<Transitions>(),
SyncTransactionQueue(TransactionPool(), mainExecutor),
mock<IWindowManager>(),
- mock<BubbleProperties>(),
+ BubbleResizabilityChecker()
)
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index d4cbe6e10971..1b0e11f7103c 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -30,13 +30,13 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.protolog.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
-import com.android.wm.shell.bubbles.DeviceConfig
import com.android.wm.shell.shared.bubbles.BaseBubblePinController
import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
import com.android.wm.shell.shared.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.shared.bubbles.BubbleBarLocation.LEFT
import com.android.wm.shell.shared.bubbles.BubbleBarLocation.RIGHT
+import com.android.wm.shell.shared.bubbles.DeviceConfig
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestSyncExecutor.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestSyncExecutor.kt
new file mode 100644
index 000000000000..50d9f77389c8
--- /dev/null
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/common/TestSyncExecutor.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common
+
+class TestSyncExecutor : ShellExecutor {
+ override fun execute(runnable: Runnable) {
+ runnable.run()
+ }
+
+ override fun executeDelayed(runnable: Runnable, delayMillis: Long) {
+ runnable.run()
+ }
+
+ override fun removeCallbacks(runnable: Runnable) {
+ }
+
+ override fun hasCallback(runnable: Runnable): Boolean {
+ return false
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/ids.xml b/libs/WindowManager/Shell/res/values/ids.xml
index 122cde04f8e4..c6082b3bd60f 100644
--- a/libs/WindowManager/Shell/res/values/ids.xml
+++ b/libs/WindowManager/Shell/res/values/ids.xml
@@ -25,6 +25,7 @@
<item type="id" name="action_move_tl_50" />
<item type="id" name="action_move_tl_30" />
<item type="id" name="action_move_rb_full" />
+ <item type="id" name="action_swap_apps" />
<!-- For saving PhysicsAnimationLayout animations/animators as view tags. -->
<item type="id" name="translation_x_dynamicanimation_tag"/>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index c29b927f61c2..a2231dd64112 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -90,6 +90,8 @@
<string name="accessibility_action_divider_left_30">Left 30%</string>
<!-- Accessibility action for moving docked stack divider to make the right screen full screen [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_right_full">Right full screen</string>
+ <!-- Accessibility action for swapping the apps around the divider (double tap action) [CHAR LIMIT=NONE] -->
+ <string name="accessibility_action_divider_swap">Swap Apps</string>
<!-- Accessibility action for moving docked stack divider to make the top screen full screen [CHAR LIMIT=NONE] -->
<string name="accessibility_action_divider_top_full">Top full screen</string>
diff --git a/libs/WindowManager/Shell/shared/Android.bp b/libs/WindowManager/Shell/shared/Android.bp
index 261c63948a94..af46ca298efe 100644
--- a/libs/WindowManager/Shell/shared/Android.bp
+++ b/libs/WindowManager/Shell/shared/Android.bp
@@ -74,6 +74,7 @@ java_library {
"**/desktopmode/*.kt",
],
static_libs: [
+ "WindowManager-Shell-shared-AOSP",
"com.android.window.flags.window-aconfig-java",
"wm_shell-shared-utils",
],
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
index 929330918174..f479da051e06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/DeviceConfig.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/DeviceConfig.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.bubbles
+package com.android.wm.shell.shared.bubbles
import android.content.Context
import android.content.res.Configuration
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index e196880aad0f..da62be7f142f 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -18,6 +18,8 @@ package com.android.wm.shell.shared.desktopmode;
import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED;
+import static com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper.enableBubbleToFullscreen;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -230,7 +232,7 @@ public class DesktopModeStatus {
* Return {@code true} if desktop mode dev option should be shown on current device
*/
public static boolean canShowDesktopExperienceDevOption(@NonNull Context context) {
- return Flags.showDesktopExperienceDevOption();
+ return Flags.showDesktopExperienceDevOption() && isDeviceEligibleForDesktopMode(context);
}
/** Returns if desktop mode dev option should be enabled if there is no user override. */
@@ -270,7 +272,8 @@ public class DesktopModeStatus {
* necessarily enabling desktop mode
*/
public static boolean overridesShowAppHandle(@NonNull Context context) {
- return Flags.showAppHandleLargeScreens() && deviceHasLargeScreen(context);
+ return (Flags.showAppHandleLargeScreens() || enableBubbleToFullscreen())
+ && deviceHasLargeScreen(context);
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index c40a276cb7bd..947dbd276d3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -72,12 +72,18 @@ import java.util.concurrent.Executor;
public class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
- /** A string suffix used in app bubbles' {@link #mKey}. */
+ /** A string prefix used in app bubbles' {@link #mKey}. */
public static final String KEY_APP_BUBBLE = "key_app_bubble";
+ /** A string prefix used in note bubbles' {@link #mKey}. */
+ public static final String KEY_NOTE_BUBBLE = "key_note_bubble";
+
/** Whether the bubble is an app bubble. */
private final boolean mIsAppBubble;
+ /** Whether the bubble is a notetaking bubble. */
+ private final boolean mIsNoteBubble;
+
private final String mKey;
@Nullable
private final String mGroupKey;
@@ -245,6 +251,7 @@ public class Bubble implements BubbleViewProvider {
mTaskId = taskId;
mBubbleMetadataFlagListener = listener;
mIsAppBubble = false;
+ mIsNoteBubble = false;
}
private Bubble(
@@ -252,6 +259,7 @@ public class Bubble implements BubbleViewProvider {
UserHandle user,
@Nullable Icon icon,
boolean isAppBubble,
+ boolean isNoteBubble,
String key,
@ShellMainThread Executor mainExecutor,
@ShellBackgroundThread Executor bgExecutor) {
@@ -261,6 +269,7 @@ public class Bubble implements BubbleViewProvider {
mUser = user;
mIcon = icon;
mIsAppBubble = isAppBubble;
+ mIsNoteBubble = isNoteBubble;
mKey = key;
mShowBubbleUpdateDot = false;
mMainExecutor = mainExecutor;
@@ -279,6 +288,7 @@ public class Bubble implements BubbleViewProvider {
mUser = info.getUserHandle();
mIcon = info.getIcon();
mIsAppBubble = false;
+ mIsNoteBubble = false;
mKey = getBubbleKeyForShortcut(info);
mShowBubbleUpdateDot = false;
mMainExecutor = mainExecutor;
@@ -303,6 +313,7 @@ public class Bubble implements BubbleViewProvider {
mUser = user;
mIcon = icon;
mIsAppBubble = true;
+ mIsNoteBubble = false;
mKey = key;
mShowBubbleUpdateDot = false;
mMainExecutor = mainExecutor;
@@ -313,6 +324,17 @@ public class Bubble implements BubbleViewProvider {
mPackageName = task.baseActivity.getPackageName();
}
+ /** Creates a notetaking bubble. */
+ public static Bubble createNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon,
+ @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
+ return new Bubble(intent,
+ user,
+ icon,
+ /* isAppBubble= */ true,
+ /* isNoteBubble= */ true,
+ /* key= */ getNoteBubbleKeyForApp(intent.getPackage(), user),
+ mainExecutor, bgExecutor);
+ }
/** Creates an app bubble. */
public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon,
@@ -321,6 +343,7 @@ public class Bubble implements BubbleViewProvider {
user,
icon,
/* isAppBubble= */ true,
+ /* isNoteBubble= */ false,
/* key= */ getAppBubbleKeyForApp(intent.getPackage(), user),
mainExecutor, bgExecutor);
}
@@ -353,6 +376,16 @@ public class Bubble implements BubbleViewProvider {
}
/**
+ * Returns the key for a note bubble from an app with package name, {@code packageName} on an
+ * Android user, {@code user}.
+ */
+ public static String getNoteBubbleKeyForApp(String packageName, UserHandle user) {
+ Objects.requireNonNull(packageName);
+ Objects.requireNonNull(user);
+ return KEY_NOTE_BUBBLE + ":" + user.getIdentifier() + ":" + packageName;
+ }
+
+ /**
* Returns the key for a shortcut bubble using {@code packageName}, {@code user}, and the
* {@code shortcutInfo} id.
*/
@@ -375,6 +408,7 @@ public class Bubble implements BubbleViewProvider {
final Bubbles.PendingIntentCanceledListener intentCancelListener,
@ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
mIsAppBubble = false;
+ mIsNoteBubble = false;
mKey = entry.getKey();
mGroupKey = entry.getGroupKey();
mLocusId = entry.getLocusId();
@@ -1122,12 +1156,19 @@ public class Bubble implements BubbleViewProvider {
}
/**
- * Returns whether this bubble is from an app versus a notification.
+ * Returns whether this bubble is from an app (as well as notetaking) versus a notification.
*/
public boolean isAppBubble() {
return mIsAppBubble;
}
+ /**
+ * Returns whether this bubble is specific from the notetaking API.
+ */
+ public boolean isNoteBubble() {
+ return mIsNoteBubble;
+ }
+
/** Creates open app settings intent */
public Intent getSettingsIntent(final Context context) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index e3f8e0c321a4..5aed9e910dd3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -49,7 +49,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
@@ -95,7 +94,6 @@ import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
-import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -117,6 +115,7 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
@@ -205,9 +204,9 @@ public class BubbleController implements ConfigurationChangeListener,
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final IWindowManager mWmService;
- private final BubbleProperties mBubbleProperties;
private final BubbleTaskViewFactory mBubbleTaskViewFactory;
private final BubbleExpandedViewManager mExpandedViewManager;
+ private final ResizabilityChecker mResizabilityChecker;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -323,7 +322,7 @@ public class BubbleController implements ConfigurationChangeListener,
Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
- BubbleProperties bubbleProperties) {
+ ResizabilityChecker resizabilityChecker) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -372,7 +371,6 @@ public class BubbleController implements ConfigurationChangeListener,
mDragAndDropController = dragAndDropController;
mSyncQueue = syncQueue;
mWmService = wmService;
- mBubbleProperties = bubbleProperties;
shellInit.addInitCallback(this::onInit, this);
mBubbleTaskViewFactory = new BubbleTaskViewFactory() {
@Override
@@ -385,6 +383,7 @@ public class BubbleController implements ConfigurationChangeListener,
}
};
mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this);
+ mResizabilityChecker = resizabilityChecker;
}
private void registerOneHandedState(OneHandedController oneHanded) {
@@ -590,8 +589,7 @@ public class BubbleController implements ConfigurationChangeListener,
* <p>If bubble bar is supported, bubble views will be updated to switch to bar mode.
*/
public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) {
- mBubbleProperties.refresh();
- if (canShowAsBubbleBar() && listener != null) {
+ if (Flags.enableBubbleBar() && mBubblePositioner.isLargeScreen() && listener != null) {
// Only set the listener if we can show the bubble bar.
mBubbleStateListener = listener;
setUpBubbleViewsForMode();
@@ -608,7 +606,6 @@ public class BubbleController implements ConfigurationChangeListener,
* will be updated accordingly.
*/
public void unregisterBubbleStateListener() {
- mBubbleProperties.refresh();
if (mBubbleStateListener != null) {
mBubbleStateListener = null;
setUpBubbleViewsForMode();
@@ -766,14 +763,11 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
- /** Whether bubbles are showing in the bubble bar. */
+ /** Whether bubbles would be shown with the bubble bar UI. */
public boolean isShowingAsBubbleBar() {
- return canShowAsBubbleBar() && mBubbleStateListener != null;
- }
-
- /** Whether the current configuration supports showing as bubble bar. */
- private boolean canShowAsBubbleBar() {
- return mBubbleProperties.isBubbleBarEnabled() && mBubblePositioner.isLargeScreen();
+ return Flags.enableBubbleBar()
+ && mBubblePositioner.isLargeScreen()
+ && mBubbleStateListener != null;
}
/**
@@ -782,7 +776,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
@Nullable
public BubbleBarLocation getBubbleBarLocation() {
- if (canShowAsBubbleBar()) {
+ if (isShowingAsBubbleBar()) {
return mBubblePositioner.getBubbleBarLocation();
}
return null;
@@ -793,7 +787,7 @@ public class BubbleController implements ConfigurationChangeListener,
*/
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation,
@BubbleBarLocation.UpdateSource int source) {
- if (canShowAsBubbleBar()) {
+ if (isShowingAsBubbleBar()) {
BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation();
mBubblePositioner.setBubbleBarLocation(bubbleBarLocation);
if (mLayerView != null && !mLayerView.isExpandedViewDragged()) {
@@ -845,7 +839,7 @@ public class BubbleController implements ConfigurationChangeListener,
* {@link #setBubbleBarLocation(BubbleBarLocation, int)}.
*/
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
- if (canShowAsBubbleBar()) {
+ if (isShowingAsBubbleBar()) {
mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation);
}
}
@@ -1560,78 +1554,80 @@ public class BubbleController implements ConfigurationChangeListener,
/**
* This method has different behavior depending on:
- * - if an app bubble exists
- * - if an app bubble is expanded
+ * - if a notes bubble exists
+ * - if a notes bubble is expanded
*
- * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * If no notes bubble exists, this will add and expand a bubble with the provided intent. The
* intent must be explicit (i.e. include a package name or fully qualified component class name)
* and the activity for it should be resizable.
*
- * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
- * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * If a notes bubble exists, this will toggle the visibility of it, i.e. if the notes bubble is
+ * expanded, calling this method will collapse it. If the notes bubble is not expanded, calling
* this method will expand it.
*
* These bubbles are <b>not</b> backed by a notification and remain until the user dismisses
* the bubble or bubble stack.
*
- * Some notes:
- * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
- * tracked in b/273533235.
- * - Calling this method with a different intent than the existing app bubble will do nothing
+ * Some details:
+ * - Calling this method with a different intent than the existing bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
* @param user the {@link UserHandle} of the user to start this activity for.
* @param icon the {@link Icon} to use for the bubble view.
*/
- public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
+ public void showOrHideNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
if (intent == null || intent.getPackage() == null) {
- Log.w(TAG, "App bubble failed to show, invalid intent: " + intent
+ Log.w(TAG, "Notes bubble failed to show, invalid intent: " + intent
+ ((intent != null) ? " with package: " + intent.getPackage() : " "));
return;
}
- String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
+ String noteBubbleKey = Bubble.getNoteBubbleKeyForApp(intent.getPackage(), user);
PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
- if (!isResizableActivity(intent, packageManager, appBubbleKey)) return; // logs errors
+ if (!mResizabilityChecker.isResizableActivity(intent, packageManager, noteBubbleKey)) {
+ // resize check logs any errors
+ return;
+ }
- Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey);
+ Bubble existingNotebubble = mBubbleData.getBubbleInStackWithKey(noteBubbleKey);
ProtoLog.d(WM_SHELL_BUBBLES,
- "showOrHideAppBubble, key=%s existingAppBubble=%s stackVisibility=%s "
+ "showOrHideNotesBubble, key=%s existingAppBubble=%s stackVisibility=%s "
+ "statusBarShade=%s",
- appBubbleKey, existingAppBubble,
+ noteBubbleKey, existingNotebubble,
(mStackView != null ? mStackView.getVisibility() : "null"),
mIsStatusBarShade);
- if (existingAppBubble != null) {
+ if (existingNotebubble != null) {
BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
if (isStackExpanded()) {
- if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
- ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", appBubbleKey);
- // App bubble is expanded, lets collapse
+ if (selectedBubble != null && noteBubbleKey.equals(selectedBubble.getKey())) {
+ ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", noteBubbleKey);
+ // Notes bubble is expanded, lets collapse
collapseStack();
} else {
- ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", appBubbleKey);
- // App bubble is not selected, select it
- mBubbleData.setSelectedBubble(existingAppBubble);
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", noteBubbleKey);
+ // Notes bubble is not selected, select it
+ mBubbleData.setSelectedBubble(existingNotebubble);
}
} else {
- ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", appBubbleKey);
- // App bubble is not selected, select it & expand
- mBubbleData.setSelectedBubbleAndExpandStack(existingAppBubble);
+ ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", noteBubbleKey);
+ // Notes bubble is not selected, select it & expand
+ mBubbleData.setSelectedBubbleAndExpandStack(existingNotebubble);
}
} else {
// Check if it exists in the overflow
- Bubble b = mBubbleData.getOverflowBubbleWithKey(appBubbleKey);
+ Bubble b = mBubbleData.getOverflowBubbleWithKey(noteBubbleKey);
if (b != null) {
// It's in the overflow, so remove it & reinflate
- mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
+ mBubbleData.dismissBubbleWithKey(noteBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL);
// Update the bubble entry in the overflow with the latest intent.
b.setAppBubbleIntent(intent);
} else {
- // App bubble does not exist, lets add and expand it
- b = Bubble.createAppBubble(intent, user, icon, mMainExecutor, mBackgroundExecutor);
+ // Notes bubble does not exist, lets add and expand it
+ b = Bubble.createNotesBubble(intent, user, icon, mMainExecutor,
+ mBackgroundExecutor);
}
- ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey);
+ ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", noteBubbleKey);
b.setShouldAutoExpand(true);
inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
}
@@ -1679,9 +1675,9 @@ public class BubbleController implements ConfigurationChangeListener,
}
}
- /** Sets the app bubble's taskId which is cached for SysUI. */
- public void setAppBubbleTaskId(String key, int taskId) {
- mImpl.mCachedState.setAppBubbleTaskId(key, taskId);
+ /** Sets the note bubble's taskId which is cached for SysUI. */
+ public void setNoteBubbleTaskId(String key, int taskId) {
+ mImpl.mCachedState.setNoteBubbleTaskId(key, taskId);
}
/**
@@ -2539,7 +2535,7 @@ public class BubbleController implements ConfigurationChangeListener,
* @param context the context to use.
* @param entry the entry to bubble.
*/
- static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
+ boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) return true;
PendingIntent intent = entry.getBubbleMetadata() != null
? entry.getBubbleMetadata().getIntent()
@@ -2554,26 +2550,8 @@ public class BubbleController implements ConfigurationChangeListener,
}
PackageManager packageManager = getPackageManagerForUser(
context, entry.getStatusBarNotification().getUser().getIdentifier());
- return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
- }
-
- static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
- if (intent == null) {
- Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
- return false;
- }
- ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
- if (info == null) {
- Log.w(TAG, "Unable to send as bubble: " + key
- + " couldn't find activity info for intent: " + intent);
- return false;
- }
- if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
- Log.w(TAG, "Unable to send as bubble: " + key
- + " activity is not resizable for intent: " + intent);
- return false;
- }
- return true;
+ return mResizabilityChecker.isResizableActivity(intent.getIntent(), packageManager,
+ entry.getKey());
}
static PackageManager getPackageManagerForUser(Context context, int userId) {
@@ -2805,7 +2783,7 @@ public class BubbleController implements ConfigurationChangeListener,
private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
- private HashMap<String, Integer> mAppBubbleTaskIds = new HashMap();
+ private HashMap<String, Integer> mNoteBubbleTaskIds = new HashMap();
private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();
@@ -2837,20 +2815,20 @@ public class BubbleController implements ConfigurationChangeListener,
mSuppressedBubbleKeys.clear();
mShortcutIdToBubble.clear();
- mAppBubbleTaskIds.clear();
+ mNoteBubbleTaskIds.clear();
for (Bubble b : mTmpBubbles) {
mShortcutIdToBubble.put(b.getShortcutId(), b);
updateBubbleSuppressedState(b);
- if (b.isAppBubble()) {
- mAppBubbleTaskIds.put(b.getKey(), b.getTaskId());
+ if (b.isNoteBubble()) {
+ mNoteBubbleTaskIds.put(b.getKey(), b.getTaskId());
}
}
}
- /** Sets the app bubble's taskId which is cached for SysUI. */
- synchronized void setAppBubbleTaskId(String key, int taskId) {
- mAppBubbleTaskIds.put(key, taskId);
+ /** Sets the note bubble's taskId which is cached for SysUI. */
+ synchronized void setNoteBubbleTaskId(String key, int taskId) {
+ mNoteBubbleTaskIds.put(key, taskId);
}
/**
@@ -2902,7 +2880,7 @@ public class BubbleController implements ConfigurationChangeListener,
pw.println(" suppressing: " + key);
}
- pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
+ pw.println("mNoteBubbleTaskIds: " + mNoteBubbleTaskIds.values());
}
}
@@ -2953,14 +2931,14 @@ public class BubbleController implements ConfigurationChangeListener,
}
@Override
- public void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
+ public void showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon) {
mMainExecutor.execute(
- () -> BubbleController.this.showOrHideAppBubble(intent, user, icon));
+ () -> BubbleController.this.showOrHideNotesBubble(intent, user, icon));
}
@Override
- public boolean isAppBubbleTaskId(int taskId) {
- return mCachedState.mAppBubbleTaskIds.values().contains(taskId);
+ public boolean isNoteBubbleTaskId(int taskId) {
+ return mCachedState.mNoteBubbleTaskIds.values().contains(taskId);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index 97b03a9f58e4..ac74a42d1359 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -285,9 +285,9 @@ public class BubbleExpandedView extends LinearLayout {
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
- if (mBubble != null && mBubble.isAppBubble()) {
+ if (mBubble != null && mBubble.isNoteBubble()) {
// Let the controller know sooner what the taskId is.
- mManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+ mManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
}
// With the task org, the taskAppeared callback will only happen once the task has
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
index a02623138f1e..6be49ddc549a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedViewManager.kt
@@ -28,7 +28,7 @@ interface BubbleExpandedViewManager {
fun promoteBubbleFromOverflow(bubble: Bubble)
fun removeBubble(key: String, reason: Int)
fun dismissBubble(bubble: Bubble, reason: Int)
- fun setAppBubbleTaskId(key: String, taskId: Int)
+ fun setNoteBubbleTaskId(key: String, taskId: Int)
fun isStackExpanded(): Boolean
fun isShowingAsBubbleBar(): Boolean
fun hideCurrentInputMethod()
@@ -73,8 +73,8 @@ interface BubbleExpandedViewManager {
controller.dismissBubble(bubble, reason)
}
- override fun setAppBubbleTaskId(key: String, taskId: Int) {
- controller.setAppBubbleTaskId(key, taskId)
+ override fun setNoteBubbleTaskId(key: String, taskId: Int) {
+ controller.setNoteBubbleTaskId(key, taskId)
}
override fun isStackExpanded(): Boolean = controller.isStackExpanded
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index a725e04d3f8a..8cf3f7afd46a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -33,6 +33,7 @@ import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconNormalizer;
import com.android.wm.shell.R;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
/**
* Keeps track of display size, configuration, and specific bubble sizes. One place for all
@@ -758,20 +759,20 @@ public class BubblePositioner {
* is being shown, for a normal bubble.
*/
public PointF getDefaultStartPosition() {
- return getDefaultStartPosition(false /* isAppBubble */);
+ return getDefaultStartPosition(false /* isNoteBubble */);
}
/**
* The stack position to use if we don't have a saved location or if user education
* is being shown.
*
- * @param isAppBubble whether this start position is for an app bubble or not.
+ * @param isNoteBubble whether this start position is for a note bubble or not.
*/
- public PointF getDefaultStartPosition(boolean isAppBubble) {
+ public PointF getDefaultStartPosition(boolean isNoteBubble) {
// Normal bubbles start on the left if we're in LTR, right otherwise.
// TODO (b/294284894): update language around "app bubble" here
// App bubbles start on the right in RTL, left otherwise.
- final boolean startOnLeft = isAppBubble ? mDeviceConfig.isRtl() : !mDeviceConfig.isRtl();
+ final boolean startOnLeft = isNoteBubble ? mDeviceConfig.isRtl() : !mDeviceConfig.isRtl();
return getStartPosition(startOnLeft ? StackPinnedEdge.LEFT : StackPinnedEdge.RIGHT);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleResizabilityChecker.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleResizabilityChecker.kt
new file mode 100644
index 000000000000..6ca08215152f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleResizabilityChecker.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.util.Log
+
+/**
+ * Checks if an intent is resizable to display in a bubble.
+ */
+class BubbleResizabilityChecker : ResizabilityChecker {
+
+ override fun isResizableActivity(
+ intent: Intent?,
+ packageManager: PackageManager, key: String
+ ): Boolean {
+ if (intent == null) {
+ Log.w(TAG, "Unable to send as bubble: $key null intent")
+ return false
+ }
+ val info = intent.resolveActivityInfo(packageManager, 0)
+ if (info == null) {
+ Log.w(
+ TAG, ("Unable to send as bubble: " + key
+ + " couldn't find activity info for intent: " + intent)
+ )
+ return false
+ }
+ if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
+ Log.w(
+ TAG, ("Unable to send as bubble: " + key
+ + " activity is not resizable for intent: " + intent)
+ )
+ return false
+ }
+ return true
+ }
+
+ companion object {
+ private const val TAG = "BubbleResizeChecker"
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f1f49eda75b6..3babc0d801c4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -91,6 +91,7 @@ import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import com.android.wm.shell.shared.bubbles.RelativeTouchListener;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
@@ -1975,12 +1976,11 @@ public class BubbleStackView extends FrameLayout
return;
}
- if (firstBubble && bubble.isAppBubble() && !mPositioner.hasUserModifiedDefaultPosition()) {
- // TODO (b/294284894): update language around "app bubble" here
- // If it's an app bubble and we don't have a previous resting position, update the
- // controllers to use the default position for the app bubble (it'd be different from
+ if (firstBubble && bubble.isNoteBubble() && !mPositioner.hasUserModifiedDefaultPosition()) {
+ // If it's an note bubble and we don't have a previous resting position, update the
+ // controllers to use the default position for the note bubble (it'd be different from
// the position initialized with the controllers originally).
- PointF startPosition = mPositioner.getDefaultStartPosition(true /* isAppBubble */);
+ PointF startPosition = mPositioner.getDefaultStartPosition(true /* isNoteBubble */);
mStackOnLeftOrWillBe = mPositioner.isStackOnLeft(startPosition);
mStackAnimationController.setStackPosition(startPosition);
mExpandedAnimationController.setCollapsePoint(startPosition);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
index a6b858500dcb..83d311ed6cd9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java
@@ -167,9 +167,9 @@ public class BubbleTaskViewHelper {
// The taskId is saved to use for removeTask, preventing appearance in recent tasks.
mTaskId = taskId;
- if (mBubble != null && mBubble.isAppBubble()) {
+ if (mBubble != null && mBubble.isNoteBubble()) {
// Let the controller know sooner what the taskId is.
- mExpandedViewManager.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
+ mExpandedViewManager.setNoteBubbleTaskId(mBubble.getKey(), mTaskId);
}
// With the task org, the taskAppeared callback will only happen once the task has
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 4297fac0f6a8..44ae74479949 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -135,33 +135,31 @@ public interface Bubbles {
/**
* This method has different behavior depending on:
- * - if an app bubble exists
- * - if an app bubble is expanded
+ * - if a notes bubble exists
+ * - if a notes bubble is expanded
*
- * If no app bubble exists, this will add and expand a bubble with the provided intent. The
+ * If no notes bubble exists, this will add and expand a bubble with the provided intent. The
* intent must be explicit (i.e. include a package name or fully qualified component class name)
* and the activity for it should be resizable.
*
- * If an app bubble exists, this will toggle the visibility of it, i.e. if the app bubble is
- * expanded, calling this method will collapse it. If the app bubble is not expanded, calling
+ * If a notes bubble exists, this will toggle the visibility of it, i.e. if the notes bubble is
+ * expanded, calling this method will collapse it. If the notes bubble is not expanded, calling
* this method will expand it.
*
* These bubbles are <b>not</b> backed by a notification and remain until the user dismisses
* the bubble or bubble stack.
*
- * Some notes:
- * - Only one app bubble is supported at a time, regardless of users. Multi-users support is
- * tracked in b/273533235.
- * - Calling this method with a different intent than the existing app bubble will do nothing
+ * Some details:
+ * - Calling this method with a different intent than the existing bubble will do nothing
*
* @param intent the intent to display in the bubble expanded view.
- * @param user the {@link UserHandle} of the user to start this activity for.
- * @param icon the {@link Icon} to use for the bubble view.
+ * @param user the {@link UserHandle} of the user to start this activity for.
+ * @param icon the {@link Icon} to use for the bubble view.
*/
- void showOrHideAppBubble(Intent intent, UserHandle user, @Nullable Icon icon);
+ void showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon);
/** @return true if the specified {@code taskId} corresponds to app bubble's taskId. */
- boolean isAppBubbleTaskId(int taskId);
+ boolean isNoteBubbleTaskId(int taskId);
/**
` * @return a {@link SynchronousScreenCaptureListener} after performing a screenshot that may
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ResizabilityChecker.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ResizabilityChecker.kt
new file mode 100644
index 000000000000..1ccc05d38d80
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ResizabilityChecker.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles
+
+import android.content.Intent
+import android.content.pm.PackageManager
+
+/**
+ * Interface to check whether the activity backed by a specific intent is resizable.
+ */
+interface ResizabilityChecker {
+
+ /**
+ * Returns whether the provided intent represents a resizable activity.
+ *
+ * @param intent the intent to check
+ * @param packageManager the package manager to use to do the look up
+ * @param key a key representing thing being checked (used for error logging)
+ */
+ fun isResizableActivity(intent: Intent?, packageManager: PackageManager, key: String): Boolean
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index f3f8d6f96a42..b1035bc177a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -45,11 +45,11 @@ import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.bubbles.BubbleViewProvider;
-import com.android.wm.shell.bubbles.DeviceConfig;
import com.android.wm.shell.bubbles.DismissViewUtils;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedViewDragController.DragListener;
import com.android.wm.shell.shared.bubbles.BaseBubblePinController;
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
+import com.android.wm.shell.shared.bubbles.DeviceConfig;
import com.android.wm.shell.shared.bubbles.DismissView;
import kotlin.Unit;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
deleted file mode 100644
index 4206d9320b7d..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/BubbleProperties.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles.properties
-
-/**
- * An interface for exposing bubble properties via flags which can be controlled easily in tests.
- */
-interface BubbleProperties {
- /**
- * Whether bubble bar is enabled.
- *
- * When this is `true`, depending on additional factors, such as screen size and taskbar state,
- * bubbles will be displayed in the bubble bar instead of floating.
- *
- * When this is `false`, bubbles will be floating.
- */
- val isBubbleBarEnabled: Boolean
-
- /** Refreshes the current value of [isBubbleBarEnabled]. */
- fun refresh()
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
deleted file mode 100644
index 33b61b164988..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/properties/ProdBubbleProperties.kt
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.bubbles.properties
-
-import android.os.SystemProperties
-import com.android.wm.shell.Flags
-
-/** Provides bubble properties in production. */
-object ProdBubbleProperties : BubbleProperties {
-
- private var _isBubbleBarEnabled = Flags.enableBubbleBar() ||
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
-
- override val isBubbleBarEnabled
- get() = _isBubbleBarEnabled
-
- override fun refresh() {
- _isBubbleBarEnabled = Flags.enableBubbleBar() ||
- SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false)
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
new file mode 100644
index 000000000000..c10c2c905c97
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipDesktopState.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.window.flags.Flags;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.pip2.phone.PipTransition;
+
+import java.util.Optional;
+
+/** Helper class for PiP on Desktop Mode. */
+public class PipDesktopState {
+ private final PipDisplayLayoutState mPipDisplayLayoutState;
+ private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
+ private final Optional<DesktopWallpaperActivityTokenProvider>
+ mDesktopWallpaperActivityTokenProviderOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+
+ public PipDesktopState(PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ mPipDisplayLayoutState = pipDisplayLayoutState;
+ mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
+ mDesktopWallpaperActivityTokenProviderOptional =
+ desktopWallpaperActivityTokenProviderOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ }
+
+ /**
+ * Returns whether PiP in Desktop Windowing is enabled by checking the following:
+ * - Desktop Windowing in PiP flag is enabled
+ * - DesktopWallpaperActivityTokenProvider is injected
+ * - DesktopUserRepositories is injected
+ */
+ public boolean isDesktopWindowingPipEnabled() {
+ return Flags.enableDesktopWindowingPip()
+ && mDesktopWallpaperActivityTokenProviderOptional.isPresent()
+ && mDesktopUserRepositoriesOptional.isPresent();
+ }
+
+ /** Returns whether PiP in Connected Displays is enabled by checking the flag. */
+ public boolean isConnectedDisplaysPipEnabled() {
+ return Flags.enableConnectedDisplaysPip();
+ }
+
+ /** Returns whether the display with the PiP task is in freeform windowing mode. */
+ private boolean isDisplayInFreeform() {
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mPipDisplayLayoutState.getDisplayId());
+ if (tdaInfo != null) {
+ return tdaInfo.configuration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FREEFORM;
+ }
+ return false;
+ }
+
+ /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
+ private boolean isPipExitingToDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) > 0
+ || getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId)
+ || isDisplayInFreeform();
+ }
+
+ /** Returns whether {@param pipTask} would be entering in a Desktop Mode session. */
+ public boolean isPipEnteringInDesktopMode(ActivityManager.RunningTaskInfo pipTask) {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final DesktopRepository desktopRepository = getDesktopRepository();
+ return desktopRepository.getVisibleTaskCount(pipTask.getDisplayId()) > 0
+ || desktopRepository.isMinimizedPipPresentInDisplay(pipTask.getDisplayId());
+ }
+
+ /**
+ * Invoked when an EXIT_PiP transition is detected in {@link PipTransition}.
+ * Returns whether the PiP exiting should also trigger the active Desktop Mode session to exit.
+ */
+ public boolean shouldExitPipExitDesktopMode() {
+ // Early return if PiP in Desktop Windowing is not supported.
+ if (!isDesktopWindowingPipEnabled()) {
+ return false;
+ }
+ final int displayId = mPipDisplayLayoutState.getDisplayId();
+ return getDesktopRepository().getVisibleTaskCount(displayId) == 0
+ && getDesktopWallpaperActivityTokenProvider().isWallpaperActivityVisible(displayId);
+ }
+
+ /**
+ * Returns a {@link WindowContainerTransaction} that reorders the {@link WindowContainerToken}
+ * of the DesktopWallpaperActivity for the display with the given {@param displayId}.
+ */
+ public WindowContainerTransaction getWallpaperActivityTokenWct(int displayId) {
+ return new WindowContainerTransaction().reorder(
+ getDesktopWallpaperActivityTokenProvider().getToken(displayId), /* onTop= */ false);
+ }
+
+ /**
+ * The windowing mode to restore to when resizing out of PIP direction.
+ * Defaults to undefined and can be overridden to restore to an alternate windowing mode.
+ */
+ public int getOutPipWindowingMode() {
+ // If we are exiting PiP while the device is in Desktop mode (the task should expand to
+ // freeform windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
+ if (isPipExitingToDesktopMode()) {
+ if (isDisplayInFreeform()) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
+ return WINDOWING_MODE_UNDEFINED;
+ }
+
+ private DesktopRepository getDesktopRepository() {
+ return mDesktopUserRepositoriesOptional.get().getCurrent();
+ }
+
+ private DesktopWallpaperActivityTokenProvider getDesktopWallpaperActivityTokenProvider() {
+ return mDesktopWallpaperActivityTokenProviderOptional.get();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2c418d34f09a..06044ccc1c61 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -125,11 +125,13 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
}
};
- private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
+ final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
+ info.addAction(new AccessibilityAction(R.id.action_swap_apps,
+ mContext.getString(R.string.accessibility_action_divider_swap)));
if (mSplitLayout.isLeftRightSplit()) {
info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
mContext.getString(R.string.accessibility_action_divider_left_full)));
@@ -172,6 +174,11 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
@Override
public boolean performAccessibilityAction(@NonNull View host, int action,
@Nullable Bundle args) {
+ if (action == R.id.action_swap_apps) {
+ mSplitLayout.onDoubleTappedDivider();
+ return true;
+ }
+
DividerSnapAlgorithm.SnapTarget nextTarget = null;
DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm;
if (action == R.id.action_move_tl_full) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
index 752d2fd721a5..8ab53eaa5eea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/OWNERS
@@ -1,2 +1,3 @@
# WM Shell sub-module dagger owners
-jorgegil@google.com \ No newline at end of file
+jorgegil@google.com
+madym@google.com \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index c81838f56a74..6f16e047a968 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -59,7 +59,7 @@ import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
-import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
+import com.android.wm.shell.bubbles.BubbleResizabilityChecker;
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -111,6 +111,7 @@ import com.android.wm.shell.desktopmode.education.AppToWebEducationFilter;
import com.android.wm.shell.desktopmode.education.data.AppHandleEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.education.data.AppToWebEducationDatastoreRepository;
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer;
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver;
import com.android.wm.shell.desktopmode.multidesks.RootTaskDesksOrganizer;
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository;
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer;
@@ -293,7 +294,7 @@ public abstract class WMShellModule {
transitions,
syncQueue,
wmService,
- ProdBubbleProperties.INSTANCE);
+ new BubbleResizabilityChecker());
}
//
@@ -760,6 +761,7 @@ public abstract class WMShellModule {
Optional<BubbleController> bubbleController,
OverviewToDesktopTransitionObserver overviewToDesktopTransitionObserver,
DesksOrganizer desksOrganizer,
+ DesksTransitionObserver desksTransitionObserver,
UserProfileContexts userProfileContexts,
DesktopModeCompatPolicy desktopModeCompatPolicy) {
return new DesktopTasksController(
@@ -797,6 +799,7 @@ public abstract class WMShellModule {
bubbleController,
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionObserver,
userProfileContexts,
desktopModeCompatPolicy);
}
@@ -1134,6 +1137,7 @@ public abstract class WMShellModule {
Optional<DesktopMixedTransitionHandler> desktopMixedTransitionHandler,
Optional<BackAnimationController> backAnimationController,
DesktopWallpaperActivityTokenProvider desktopWallpaperActivityTokenProvider,
+ @NonNull DesksTransitionObserver desksTransitionObserver,
ShellInit shellInit) {
return desktopUserRepositories.flatMap(
repository ->
@@ -1146,11 +1150,20 @@ public abstract class WMShellModule {
desktopMixedTransitionHandler.get(),
backAnimationController.get(),
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit)));
}
@WMSingleton
@Provides
+ static DesksTransitionObserver provideDesksTransitionObserver(
+ @NonNull @DynamicOverride DesktopUserRepositories desktopUserRepositories
+ ) {
+ return new DesksTransitionObserver(desktopUserRepositories);
+ }
+
+ @WMSingleton
+ @Provides
static Optional<DesktopMixedTransitionHandler> provideDesktopMixedTransitionHandler(
Context context,
Transitions transitions,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 413300612f7d..e7c76bbd91b2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMediaController;
import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -84,14 +85,11 @@ public abstract class Pip2Module {
@NonNull PipDisplayLayoutState pipDisplayLayoutState,
@NonNull PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
pipBoundsState, null, pipBoundsAlgorithm, pipTaskListener,
pipScheduler, pipStackListenerController, pipDisplayLayoutState,
- pipUiStateChangeController, displayController, desktopUserRepositoriesOptional,
- desktopWallpaperActivityTokenProviderOptional);
+ pipUiStateChangeController, displayController, pipDesktopState);
}
@WMSingleton
@@ -142,13 +140,9 @@ public abstract class Pip2Module {
PipBoundsState pipBoundsState,
@ShellMainThread ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
return new PipScheduler(context, pipBoundsState, mainExecutor, pipTransitionState,
- desktopUserRepositoriesOptional, desktopWallpaperActivityTokenProviderOptional,
- rootTaskDisplayAreaOrganizer);
+ pipDesktopState);
}
@WMSingleton
@@ -233,4 +227,17 @@ public abstract class Pip2Module {
return new PipTaskListener(context, shellTaskOrganizer, pipTransitionState,
pipScheduler, pipBoundsState, pipBoundsAlgorithm, mainExecutor);
}
+
+ @WMSingleton
+ @Provides
+ static PipDesktopState providePipDesktopState(
+ PipDisplayLayoutState pipDisplayLayoutState,
+ Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
+ Optional<DesktopWallpaperActivityTokenProvider>
+ desktopWallpaperActivityTokenProviderOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer
+ ) {
+ return new PipDesktopState(pipDisplayLayoutState, desktopUserRepositoriesOptional,
+ desktopWallpaperActivityTokenProviderOptional, rootTaskDisplayAreaOrganizer);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
index 164d04bbde65..b93d2e396402 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeShellCommandHandler.kt
@@ -152,8 +152,8 @@ class DesktopModeShellCommandHandler(private val controller: DesktopTasksControl
pw.println("Error: desk id should be an integer")
return false
}
- pw.println("Not implemented.")
- return false
+ controller.removeDesk(deskId)
+ return true
}
private fun runRemoveAllDesks(args: Array<String>, pw: PrintWriter): Boolean {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 32ee319a053b..621ccba40db2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -59,6 +59,8 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
/**
* Animated visual indicator for Desktop Mode windowing transitions.
@@ -149,12 +151,19 @@ public class DesktopModeVisualIndicator {
// left, and split right for the right edge. This is universal across all drag event types.
if (inputCoordinates.x < 0) return TO_SPLIT_LEFT_INDICATOR;
if (inputCoordinates.x > layout.width()) return TO_SPLIT_RIGHT_INDICATOR;
- // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
- // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
- // indicator.
- IndicatorType result = mDragStartState == DragStartState.FROM_FREEFORM
- ? NO_INDICATOR
- : TO_DESKTOP_INDICATOR;
+ IndicatorType result;
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !DesktopModeStatus.canEnterDesktopMode(mContext)) {
+ // If desktop is not available, default to "no indicator"
+ result = NO_INDICATOR;
+ } else {
+ // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
+ // In drags not originating on a freeform caption, we should default to a TO_DESKTOP
+ // indicator.
+ result = mDragStartState == DragStartState.FROM_FREEFORM
+ ? NO_INDICATOR
+ : TO_DESKTOP_INDICATOR;
+ }
final int transitionAreaWidth = mContext.getResources().getDimensionPixelSize(
com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness);
// Because drags in freeform use task position for indicator calculation, we need to
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index 4ff1a5f1be31..043b353ba380 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -174,6 +174,9 @@ class DesktopRepository(
/** Returns the number of desks in the given display. */
fun getNumberOfDesks(displayId: Int) = desktopData.getNumberOfDesks(displayId)
+ /** Returns the display the given desk is in. */
+ fun getDisplayForDesk(deskId: Int) = desktopData.getDisplayForDesk(deskId)
+
/** Adds [regionListener] to inform about changes to exclusion regions for all Desktop tasks. */
fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
desktopGestureExclusionListener = regionListener
@@ -207,6 +210,14 @@ class DesktopRepository(
desktopData.createDesk(displayId, deskId)
}
+ /** Returns the ids of the existing desks in the given display. */
+ @VisibleForTesting
+ fun getDeskIds(displayId: Int): Set<Int> =
+ desktopData.desksSequence(displayId).map { desk -> desk.deskId }.toSet()
+
+ /** Returns the id of the default desk in the given display. */
+ fun getDefaultDeskId(displayId: Int): Int? = getDefaultDesk(displayId)?.deskId
+
/** Returns the default desk in the given display. */
private fun getDefaultDesk(displayId: Int): Desk? = desktopData.getDefaultDesk(displayId)
@@ -716,17 +727,13 @@ class DesktopRepository(
}
}
- /**
- * Removes the active desk for the given [displayId] and returns the active tasks on that desk.
- *
- * TODO: b/389960283 - add explicit [deskId] argument.
- */
- fun removeDesk(displayId: Int): ArraySet<Int> {
- val desk = desktopData.getActiveDesk(displayId)
- if (desk == null) {
- logW("Could not find desk to remove: displayId=%d", displayId)
- return ArraySet()
- }
+ /** Removes the given desk and returns the active tasks in that desk. */
+ fun removeDesk(deskId: Int): Set<Int> {
+ val desk =
+ desktopData.getDesk(deskId)
+ ?: return emptySet<Int>().also {
+ logW("Could not find desk to remove: deskId=%d", deskId)
+ }
val activeTasks = ArraySet(desk.activeTasks)
desktopData.remove(desk.deskId)
return activeTasks
@@ -1066,7 +1073,7 @@ class DesktopRepository(
}
override fun getDisplayForDesk(deskId: Int): Int =
- getAllActiveDesks().find { it.deskId == deskId }?.displayId
+ desksSequence().find { it.deskId == deskId }?.displayId
?: error("Display for desk=$deskId not found")
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 06a64f32f0bc..475515053bfe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -103,7 +103,9 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.multidesks.OnDeskRemovedListener
import com.android.wm.shell.draganddrop.DragAndDropController
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
@@ -185,6 +187,7 @@ class DesktopTasksController(
private val bubbleController: Optional<BubbleController>,
private val overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver,
private val desksOrganizer: DesksOrganizer,
+ private val desksTransitionObserver: DesksTransitionObserver,
private val userProfileContexts: UserProfileContexts,
private val desktopModeCompatPolicy: DesktopModeCompatPolicy,
) :
@@ -1525,16 +1528,11 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
if (ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER.isTrue()) {
-
- // If the wallpaper activity for this display already exists, let's reorder it to top.
- val wallpaperActivityToken = desktopWallpaperActivityTokenProvider.getToken(displayId)
- if (wallpaperActivityToken != null) {
- wct.reorder(wallpaperActivityToken, /* onTop= */ true)
- return
- }
-
val intent = Intent(context, DesktopWallpaperActivity::class.java)
- if (Flags.enablePerDisplayDesktopWallpaperActivity()) {
+ if (
+ desktopWallpaperActivityTokenProvider.getToken(displayId) == null &&
+ Flags.enablePerDisplayDesktopWallpaperActivity()
+ ) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
}
@@ -2374,20 +2372,62 @@ class DesktopTasksController(
)
}
- fun removeDesktop(displayId: Int) {
+ /** Removes the default desk in the given display. */
+ @Deprecated("Deprecated with multi-desks.", ReplaceWith("removeDesk()"))
+ fun removeDefaultDeskInDisplay(displayId: Int) {
+ val deskId =
+ checkNotNull(taskRepository.getDefaultDeskId(displayId)) {
+ "Expected a default desk to exist"
+ }
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ /** Removes the given desk. */
+ fun removeDesk(deskId: Int) {
+ val displayId = taskRepository.getDisplayForDesk(deskId)
+ removeDesk(displayId = displayId, deskId = deskId)
+ }
+
+ private fun removeDesk(displayId: Int, deskId: Int) {
if (!DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION.isTrue()) return
+ logV("removeDesk deskId=%d from displayId=%d", deskId, displayId)
- val tasksToRemove = taskRepository.removeDesk(displayId)
- val wct = WindowContainerTransaction()
- tasksToRemove.forEach {
- val task = shellTaskOrganizer.getRunningTaskInfo(it)
- if (task != null) {
- wct.removeTask(task.token)
+ val tasksToRemove =
+ if (Flags.enableMultipleDesktopsBackend()) {
+ taskRepository.getActiveTaskIdsInDesk(deskId)
} else {
- recentTasksController?.removeBackgroundTask(it)
+ // TODO: 362720497 - make sure minimized windows are also removed in WM
+ // and the repository.
+ taskRepository.removeDesk(deskId)
+ }
+
+ val wct = WindowContainerTransaction()
+ if (!Flags.enableMultipleDesktopsBackend()) {
+ tasksToRemove.forEach {
+ val task = shellTaskOrganizer.getRunningTaskInfo(it)
+ if (task != null) {
+ wct.removeTask(task.token)
+ } else {
+ recentTasksController?.removeBackgroundTask(it)
+ }
}
+ } else {
+ // TODO: 362720497 - double check background tasks are also removed.
+ desksOrganizer.removeDesk(wct, deskId)
+ }
+ if (!Flags.enableMultipleDesktopsBackend() && wct.isEmpty) return
+ val transition = transitions.startTransition(TRANSIT_CLOSE, wct, /* handler= */ null)
+ if (Flags.enableMultipleDesktopsBackend()) {
+ desksTransitionObserver.addPendingTransition(
+ DeskTransition.RemoveDesk(
+ token = transition,
+ displayId = displayId,
+ deskId = deskId,
+ tasks = tasksToRemove,
+ onDeskRemovedListener = onDeskRemovedListener,
+ )
+ )
}
- if (!wct.isEmpty) transitions.startTransition(TRANSIT_CLOSE, wct, null)
}
/** Enter split by using the focused desktop task in given `displayId`. */
@@ -3091,7 +3131,7 @@ class DesktopTasksController(
override fun removeDesktop(displayId: Int) {
executeRemoteCallWithTaskPermission(controller, "removeDesktop") { c ->
- c.removeDesktop(displayId)
+ c.removeDefaultDeskInDisplay(displayId)
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
index b3648699ed0b..3ada988ba2a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt
@@ -37,6 +37,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.isExitDesktopModeTransition
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.shared.TransitionUtil
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
@@ -58,6 +59,7 @@ class DesktopTasksTransitionObserver(
private val desktopMixedTransitionHandler: DesktopMixedTransitionHandler,
private val backAnimationController: BackAnimationController,
private val desktopWallpaperActivityTokenProvider: DesktopWallpaperActivityTokenProvider,
+ private val desksTransitionObserver: DesksTransitionObserver,
shellInit: ShellInit,
) : Transitions.TransitionObserver {
@@ -87,6 +89,7 @@ class DesktopTasksTransitionObserver(
finishTransaction: SurfaceControl.Transaction,
) {
// TODO: b/332682201 Update repository state
+ desksTransitionObserver.onTransitionReady(transition, info)
if (
DesktopModeFlags.INCLUDE_TOP_TRANSPARENT_FULLSCREEN_TASK_IN_DESKTOP_HEURISTIC
.isTrue() && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_MODALS_POLICY.isTrue()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
new file mode 100644
index 000000000000..47088c0b545a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DeskTransition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.IBinder
+
+/** Represents shell-started transitions involving desks. */
+sealed class DeskTransition {
+ /** The transition token. */
+ abstract val token: IBinder
+
+ /** A transition to remove a desk and its tasks from a display. */
+ data class RemoveDesk(
+ override val token: IBinder,
+ val displayId: Int,
+ val deskId: Int,
+ val tasks: Set<Int>,
+ val onDeskRemovedListener: OnDeskRemovedListener?,
+ ) : DeskTransition()
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
new file mode 100644
index 000000000000..3e49b8a4538b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserver.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.IBinder
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import com.android.window.flags.Flags
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+
+/**
+ * Observer of desk-related transitions, such as adding, removing or activating a whole desk. It
+ * tracks pending transitions and updates repository state once they finish.
+ */
+class DesksTransitionObserver(private val desktopUserRepositories: DesktopUserRepositories) {
+ private val deskTransitions = mutableMapOf<IBinder, DeskTransition>()
+
+ /** Adds a pending desk transition to be tracked. */
+ fun addPendingTransition(transition: DeskTransition) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ deskTransitions[transition.token] = transition
+ }
+
+ /**
+ * Called when any transition is ready, which may include transitions not tracked by this
+ * observer.
+ */
+ fun onTransitionReady(transition: IBinder, info: TransitionInfo) {
+ if (!Flags.enableMultipleDesktopsBackend()) return
+ val deskTransition = deskTransitions.remove(transition) ?: return
+ val desktopRepository = desktopUserRepositories.current
+ when (deskTransition) {
+ is DeskTransition.RemoveDesk -> {
+ check(info.type == TRANSIT_CLOSE) { "Expected close transition for desk removal" }
+ // TODO: b/362720497 - consider verifying the desk was actually removed through the
+ // DesksOrganizer. The transition info won't have changes if the desk was not
+ // visible, such as when dismissing from Overview.
+ val deskId = deskTransition.deskId
+ val displayId = deskTransition.displayId
+ desktopRepository.removeDesk(deskTransition.deskId)
+ deskTransition.onDeskRemovedListener?.onDeskRemoved(displayId, deskId)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
index faa97ac4512f..f50d253ddf42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -1,4 +1,5 @@
# Making changes in the Shell
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
index 7070dead9957..9b09904527bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -1,4 +1,5 @@
# Usage of Dagger in the Shell library
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
index 09e627c0e02c..dd5827af97d9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -1,4 +1,5 @@
# Debugging in the Shell
+[Back to home](README.md)
---
@@ -50,6 +51,11 @@ adb shell wm shell protolog enable-text TAG
adb shell wm shell protolog disable-text TAG
```
+### R8 optimizations & ProtoLog
+
+If the APK that the Shell library is included into has R8 optimizations enabled, then you may need
+to update the proguard flags to keep the generated protolog classes (ie. AOSP SystemUI's [proguard.flags](base/packages/SystemUI/proguard_common.flags)).
+
## Winscope Tracing
The Winscope tool is extremely useful in determining what is happening on-screen in both
@@ -57,25 +63,42 @@ WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help)
use the tool. This trace will contain all the information about the windows/activities/surfaces on
screen.
-## WindowManager/SurfaceFlinger hierarchy dump
+## WindowManager/SurfaceFlinger/InputDispatcher information
A quick way to view the WindowManager hierarchy without a winscope trace is via the wm dumps:
```shell
adb shell dumpsys activity containers
+# The output lists the containers in the hierarchy from top -> bottom in z-order
+```
+
+To get more information about windows on the screen:
+```shell
+# All windows in WM
+adb shell dumpsys window -a
+# The windows are listed from top -> bottom in z-order
+
+# Visible windows only
+adb shell dumpsys window -a visible
```
Likewise, the SurfaceFlinger hierarchy can be dumped for inspection by running:
```shell
adb shell dumpsys SurfaceFlinger
-# Search output for "Layer Hierarchy"
+# Search output for "Layer Hierarchy", the surfaces in the table are listed bottom -> top in z-order
+```
+
+And the visible input windows can be dumped via:
+```shell
+adb shell dumpsys input
+# Search output for "Windows:", they are ordered top -> bottom in z-order
```
## Tracing global SurfaceControl transaction updates
While Winscope traces are very useful, it sometimes doesn't give you enough information about which
part of the code is initiating the transaction updates. In such cases, it can be helpful to get
-stack traces when specific surface transaction calls are made, which is possible by enabling the
-following system properties for example:
+stack traces when specific surface transaction calls are made (regardless of process), which is
+possible by enabling the following system properties for example:
```shell
# Enabling
adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,setPosition # matches the name of the SurfaceControlTransaction methods
@@ -94,9 +117,16 @@ properties.
It is not necessary to set both `log_match_call` and `log_match_name`, but note logs can be quite
noisy if unfiltered.
-It can sometimes be useful to trace specific logs and when they are applied (sometimes we build
-transactions that can be applied later). You can do this by adding the "merge" and "apply" calls to
-the set of requested calls:
+### Tracing transaction merge & apply
+
+Tracing the method calls on SurfaceControl.Transaction tells you where a change is requested, but
+the changes are not actually committed until the transaction itself is applied. And because
+transactions can be passed across processes, or prepared in advance for later application (ie.
+when restoring state after a Transition), the ordering of the change logs is not always clear
+by itself.
+
+In such cases, you can also enable the "merge" and "apply" calls to get additional information
+about how/when transactions are respectively merged/applied:
```shell
# Enabling
adb shell setprop persist.wm.debug.sc.tx.log_match_call setAlpha,merge,apply # apply will dump logs of each setAlpha or merge call on that tx
@@ -104,6 +134,11 @@ adb reboot
adb logcat -s "SurfaceControlRegistry"
```
+Using those logs, you can first look at where the desired change is called, note the transaction
+id, and then search the logs for where that transaction id is used. If it is merged into another
+transaction, you can continue the search using the merged transaction until you find the final
+transaction which is applied.
+
## Tracing activity starts & finishes in the app process
It's sometimes useful to know when to see a stack trace of when an activity starts in the app code
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md
index 061ae00e2b25..f7707da33189 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md
@@ -1,4 +1,5 @@
# Extending the Shell for Products/OEMs
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
index b489fe8ea1a9..bed0fba453d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -1,4 +1,5 @@
# What is the WindowManager Shell
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
index 5e92010d4b68..47383b0a81a0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -1,4 +1,5 @@
# Shell & SystemUI
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
index 98af930c4486..b4553131284b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -1,4 +1,5 @@
# Testing
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
index 837a6dd32ff2..bde722357308 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -1,4 +1,5 @@
# Threading
+[Back to home](README.md)
---
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
index 21b0820f523a..e17587ff18bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipScheduler.java
@@ -16,14 +16,10 @@
package com.android.wm.shell.pip2.phone;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
-import android.window.DisplayAreaInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
@@ -32,20 +28,14 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
-import com.android.window.flags.Flags;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import java.util.Objects;
-import java.util.Optional;
-
/**
* Scheduler for Shell initiated PiP transitions and animations.
*/
@@ -56,10 +46,7 @@ public class PipScheduler {
private final PipBoundsState mPipBoundsState;
private final ShellExecutor mMainExecutor;
private final PipTransitionState mPipTransitionState;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
- private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final PipDesktopState mPipDesktopState;
private PipTransitionController mPipTransitionController;
private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
@@ -72,18 +59,12 @@ public class PipScheduler {
PipBoundsState pipBoundsState,
ShellExecutor mainExecutor,
PipTransitionState pipTransitionState,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional,
- RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer) {
+ PipDesktopState pipDesktopState) {
mContext = context;
mPipBoundsState = pipBoundsState;
mMainExecutor = mainExecutor;
mPipTransitionState = pipTransitionState;
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
- mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mPipDesktopState = pipDesktopState;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
@@ -105,7 +86,7 @@ public class PipScheduler {
wct.setBounds(pipTaskToken, null);
// if we are hitting a multi-activity case
// windowing mode change will reparent to original host task
- wct.setWindowingMode(pipTaskToken, getOutPipWindowingMode());
+ wct.setWindowingMode(pipTaskToken, mPipDesktopState.getOutPipWindowingMode());
return wct;
}
@@ -235,55 +216,6 @@ public class PipScheduler {
maybeUpdateMovementBounds();
}
- /** Returns whether the display is in freeform windowing mode. */
- private boolean isDisplayInFreeform() {
- final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
- Objects.requireNonNull(mPipTransitionState.getPipTaskInfo()).displayId);
- if (tdaInfo != null) {
- return tdaInfo.configuration.windowConfiguration.getWindowingMode()
- == WINDOWING_MODE_FREEFORM;
- }
- return false;
- }
-
- /** Returns whether PiP is exiting while we're in desktop mode. */
- private boolean isPipExitingToDesktopMode() {
- // Early return if PiP in Desktop Windowing is not supported.
- if (!Flags.enableDesktopWindowingPip() || mDesktopUserRepositoriesOptional.isEmpty()
- || mDesktopWallpaperActivityTokenProviderOptional.isEmpty()) {
- return false;
- }
- final int displayId = Objects.requireNonNull(
- mPipTransitionState.getPipTaskInfo()).displayId;
- return mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(displayId)
- > 0
- || mDesktopWallpaperActivityTokenProviderOptional.get().isWallpaperActivityVisible(
- displayId)
- || isDisplayInFreeform();
- }
-
- /**
- * The windowing mode to restore to when resizing out of PIP direction. Defaults to undefined
- * and can be overridden to restore to an alternate windowing mode.
- */
- private int getOutPipWindowingMode() {
- // If we are exiting PiP while the device is in Desktop mode (the task should expand to
- // freeform windowing mode):
- // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
- // resolve the windowing mode to the display's windowing mode.
- // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
- if (isPipExitingToDesktopMode()) {
- if (isDisplayInFreeform()) {
- return WINDOWING_MODE_UNDEFINED;
- } else {
- return WINDOWING_MODE_FREEFORM;
- }
- }
-
- // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
- return WINDOWING_MODE_UNDEFINED;
- }
-
@VisibleForTesting
void setSurfaceControlTransactionFactory(
@NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index d735375b0fc9..dbcbf3663827 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -132,8 +132,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
"onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
setPictureInPictureParams(params);
+ // Note: params is nullable while mPictureInPictureParams is never null
float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
- if (params.hasSetAspectRatio()
+ if (mPictureInPictureParams.hasSetAspectRatio()
&& mPipBoundsAlgorithm.isValidPictureInPictureAspectRatio(newAspectRatio)
&& PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 78aa686a3a0e..bb9b479524e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -56,19 +56,16 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.util.Preconditions;
-import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ComponentUtils;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.common.pip.PipDisplayLayoutState;
import com.android.wm.shell.common.pip.PipMenuController;
import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -79,8 +76,6 @@ import com.android.wm.shell.shared.pip.PipContentOverlay;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
-import java.util.Optional;
-
/**
* Implementation of transitions for PiP on phone.
*/
@@ -117,9 +112,7 @@ public class PipTransition extends PipTransitionController implements
private final PipDisplayLayoutState mPipDisplayLayoutState;
private final DisplayController mDisplayController;
private final PipSurfaceTransactionHelper mPipSurfaceTransactionHelper;
- private final Optional<DesktopUserRepositories> mDesktopUserRepositoriesOptional;
- private final Optional<DesktopWallpaperActivityTokenProvider>
- mDesktopWallpaperActivityTokenProviderOptional;
+ private final PipDesktopState mPipDesktopState;
//
// Transition caches
@@ -159,9 +152,7 @@ public class PipTransition extends PipTransitionController implements
PipDisplayLayoutState pipDisplayLayoutState,
PipUiStateChangeController pipUiStateChangeController,
DisplayController displayController,
- Optional<DesktopUserRepositories> desktopUserRepositoriesOptional,
- Optional<DesktopWallpaperActivityTokenProvider>
- desktopWallpaperActivityTokenProviderOptional) {
+ PipDesktopState pipDesktopState) {
super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
pipBoundsAlgorithm);
@@ -174,9 +165,7 @@ public class PipTransition extends PipTransitionController implements
mPipDisplayLayoutState = pipDisplayLayoutState;
mDisplayController = displayController;
mPipSurfaceTransactionHelper = new PipSurfaceTransactionHelper(mContext);
- mDesktopUserRepositoriesOptional = desktopUserRepositoriesOptional;
- mDesktopWallpaperActivityTokenProviderOptional =
- desktopWallpaperActivityTokenProviderOptional;
+ mPipDesktopState = pipDesktopState;
}
@Override
@@ -802,7 +791,7 @@ public class PipTransition extends PipTransitionController implements
&& getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
}
- if (Flags.enableDesktopWindowingPip()) {
+ if (mPipDesktopState.isDesktopWindowingPipEnabled()) {
adjustedSourceRectHint.offset(-pipActivityChange.getStartAbsBounds().left,
-pipActivityChange.getStartAbsBounds().top);
}
@@ -862,7 +851,7 @@ public class PipTransition extends PipTransitionController implements
// If PiP is enabled on Connected Displays, update PipDisplayLayoutState to have the correct
// display info that PiP is entering in.
- if (Flags.enableConnectedDisplaysPip()) {
+ if (mPipDesktopState.isConnectedDisplaysPipEnabled()) {
final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(
pipTask.displayId);
if (displayLayout != null) {
@@ -910,12 +899,7 @@ public class PipTransition extends PipTransitionController implements
// Since opening a new task while in Desktop Mode always first open in Fullscreen
// until DesktopMode Shell code resolves it to Freeform, PipTransition will get a
// possibility to handle it also. In this case return false to not have it enter PiP.
- final boolean isInDesktopSession = !mDesktopUserRepositoriesOptional.isEmpty()
- && (mDesktopUserRepositoriesOptional.get().getCurrent().getVisibleTaskCount(
- pipTask.displayId) > 0
- || mDesktopUserRepositoriesOptional.get().getCurrent()
- .isMinimizedPipPresentInDisplay(pipTask.displayId));
- if (isInDesktopSession) {
+ if (mPipDesktopState.isPipEnteringInDesktopMode(pipTask)) {
return false;
}
@@ -1089,26 +1073,13 @@ public class PipTransition extends PipTransitionController implements
"Unexpected bundle for " + mPipTransitionState);
break;
case PipTransitionState.EXITED_PIP:
- final TaskInfo pipTask = mPipTransitionState.getPipTaskInfo();
- final boolean desktopPipEnabled = Flags.enableDesktopWindowingPip()
- && mDesktopUserRepositoriesOptional.isPresent()
- && mDesktopWallpaperActivityTokenProviderOptional.isPresent();
- if (desktopPipEnabled && pipTask != null) {
- final DesktopRepository desktopRepository =
- mDesktopUserRepositoriesOptional.get().getCurrent();
- final boolean wallpaperIsVisible =
- mDesktopWallpaperActivityTokenProviderOptional.get()
- .isWallpaperActivityVisible(pipTask.displayId);
- if (desktopRepository.getVisibleTaskCount(pipTask.displayId) == 0
- && wallpaperIsVisible) {
- mTransitions.startTransition(
- TRANSIT_TO_BACK,
- new WindowContainerTransaction().reorder(
- mDesktopWallpaperActivityTokenProviderOptional.get()
- .getToken(pipTask.displayId), /* onTop= */ false),
- null
- );
- }
+ if (mPipDesktopState.shouldExitPipExitDesktopMode()) {
+ mTransitions.startTransition(
+ TRANSIT_TO_BACK,
+ mPipDesktopState.getWallpaperActivityTokenWct(
+ mPipTransitionState.getPipTaskInfo().getDisplayId()),
+ null /* firstHandler */
+ );
}
mPipTransitionState.setPinnedTaskLeash(null);
mPipTransitionState.setPipTaskInfo(null);
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 6a2a7b6becdc..fb4ce13c441f 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
@@ -131,6 +131,7 @@ import com.android.wm.shell.recents.RecentsTransitionStateListener;
import com.android.wm.shell.shared.FocusTransitionListener;
import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
@@ -1439,13 +1440,17 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
mDragToDesktopAnimationStartBounds.set(
relevantDecor.mTaskInfo.configuration.windowConfiguration.getBounds());
boolean dragFromStatusBarAllowed = false;
+ final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
if (DesktopModeStatus.canEnterDesktopMode(mContext)) {
// In proto2 any full screen or multi-window task can be dragged to
// freeform.
- final int windowingMode = relevantDecor.mTaskInfo.getWindowingMode();
dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW;
}
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
+ // TODO(b/388851898): add support for split screen (multi-window wm mode)
+ dragFromStatusBarAllowed = windowingMode == WINDOWING_MODE_FULLSCREEN;
+ }
final boolean shouldStartTransitionDrag =
relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
|| DesktopModeFlags.ENABLE_HANDLE_INPUT_FIX.isTrue();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index c1a6240516c1..afb234899339 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -483,7 +483,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
boolean hasGlobalFocus, @NonNull Region displayExclusionRegion) {
Trace.beginSection("DesktopModeWindowDecoration#relayout");
- if (Flags.enableDesktopWindowingAppToWeb()) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()) {
setCapturedLink(taskInfo.capturedLink, taskInfo.capturedLinkTimestamp);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index 32a2f8294877..1d9564948772 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
@@ -252,6 +252,7 @@ class HandleMenu(
view = handleMenuView.rootView,
forciblyShownTypes = if (forceShowSystemBars) { systemBars() } else { 0 },
ignoreCutouts = Flags.showAppHandleLargeScreens()
+ || BubbleAnythingFlagHelper.enableBubbleToFullscreen()
)
} else {
parentDecor.addWindow(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 1264c013faf5..2948fdaf16af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -41,6 +41,7 @@ import com.android.internal.policy.SystemBarUtils
import com.android.window.flags.Flags
import com.android.wm.shell.R
import com.android.wm.shell.shared.animation.Interpolators
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.windowdecor.WindowManagerWrapper
import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
@@ -146,6 +147,7 @@ internal class AppHandleViewHolder(
taskInfo.taskId, handlePosition.x, handlePosition.y, handleWidth, handleHeight,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
ignoreCutouts = Flags.showAppHandleLargeScreens()
+ || BubbleAnythingFlagHelper.enableBubbleToFullscreen()
)
val view = statusBarInputLayer?.view ?: error("Unable to find statusBarInputLayer View")
val lp = statusBarInputLayer?.lp ?: error("Unable to find statusBarInputLayer " +
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
index ab1ac1a0efa3..f767861addf9 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/flicker-service/src/com/android/wm/shell/flicker/DesktopModeFlickerScenarios.kt
@@ -19,7 +19,7 @@ package com.android.wm.shell.flicker
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_HEIGHT
import android.tools.PlatformConsts.DESKTOP_MODE_MINIMUM_WINDOW_WIDTH
import android.tools.flicker.AssertionInvocationGroup
-import android.tools.flicker.assertors.assertions.AppLayerIncreasesInSize
+import android.tools.flicker.assertors.assertions.ResizeVeilKeepsIncreasingInSize
import android.tools.flicker.assertors.assertions.AppLayerIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppLayerIsVisibleAtStart
@@ -36,6 +36,7 @@ import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayHeight
import android.tools.flicker.assertors.assertions.AppWindowHasMaxDisplayWidth
import android.tools.flicker.assertors.assertions.AppWindowHasSizeOfAtLeast
import android.tools.flicker.assertors.assertions.AppWindowInsideDisplayBoundsAtEnd
+import android.tools.flicker.assertors.assertions.AppWindowIsBiggerThanInitialBoundsAtEnd
import android.tools.flicker.assertors.assertions.AppWindowIsInvisibleAtEnd
import android.tools.flicker.assertors.assertions.AppWindowIsVisibleAlways
import android.tools.flicker.assertors.assertions.AppWindowMaintainsAspectRatioAlways
@@ -168,9 +169,12 @@ class DesktopModeFlickerScenarios {
TaggedCujTransitionMatcher(associatedTransitionRequired = false)
)
.build(),
- // TODO(373638597) Add AppLayerIncreasesInSize assertion
- assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS
- )
+ assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
+ listOf(
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
+ AppWindowIsBiggerThanInitialBoundsAtEnd(DESKTOP_MODE_APP),
+ ).associateBy({ it }, { AssertionInvocationGroup.BLOCKING })
+ )
val EDGE_RESIZE =
FlickerConfigEntry(
@@ -184,7 +188,8 @@ class DesktopModeFlickerScenarios {
.build(),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
+ AppWindowIsBiggerThanInitialBoundsAtEnd(DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -223,9 +228,9 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- // TODO(373638597) Add AppLayerIncreasesInSize assertion
AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
- AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
+ AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
)
@@ -368,7 +373,7 @@ class DesktopModeFlickerScenarios {
),
assertions = AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
AppWindowHasMaxDisplayHeight(DESKTOP_MODE_APP),
AppWindowHasMaxDisplayWidth(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
@@ -393,7 +398,7 @@ class DesktopModeFlickerScenarios {
assertions =
AssertionTemplates.DESKTOP_MODE_APP_VISIBILITY_ASSERTIONS +
listOf(
- AppLayerIncreasesInSize(DESKTOP_MODE_APP),
+ ResizeVeilKeepsIncreasingInSize(DESKTOP_MODE_APP),
AppWindowMaintainsAspectRatioAlways(DESKTOP_MODE_APP),
AppWindowHasMaxBoundsInOnlyOneDimension(DESKTOP_MODE_APP)
).associateBy({ it }, { AssertionInvocationGroup.BLOCKING }),
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt
new file mode 100644
index 000000000000..cc9a799fb50c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/OpenAppWithExternalDisplayConnectedTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.OpenAppWithExternalDisplayConnected
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [OpenAppWithExternalDisplayConnected]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class OpenAppWithExternalDisplayConnectedTest : OpenAppWithExternalDisplayConnected()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
new file mode 100644
index 000000000000..81c46f13b384
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/OpenAppWithExternalDisplayConnected.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.app.Instrumentation
+import android.content.Context
+import android.hardware.display.DisplayManager
+import android.hardware.display.VirtualDisplay
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import android.util.DisplayMetrics
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.ExtendedDisplaySettingsSession
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for launching an app in desktop mode by default when an external display is
+ * connected.
+ */
+@Ignore("Test Base Class")
+abstract class OpenAppWithExternalDisplayConnected
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val displayManager =
+ instrumentation.getContext().getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ private var virtualDisplay: VirtualDisplay? = null
+
+ private val extendedDisplaySettingsSession =
+ ExtendedDisplaySettingsSession(instrumentation.context.contentResolver)
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableDisplayWindowingModeSwitching())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ extendedDisplaySettingsSession.open()
+ virtualDisplay = displayManager.createVirtualDisplay(
+ /* displayName= */ DISPLAY_NAME,
+ /* width= */ DISPLAY_WIDTH,
+ /* height= */ DISPLAY_HEIGHT,
+ /* densityDpi= */ DisplayMetrics.DENSITY_DEFAULT,
+ /* surface= */ null,
+ /* flags= */ DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY or
+ DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED
+ )
+ }
+
+ @Test
+ open fun openAppWithExternalDisplayConnected() {
+ testApp.open()
+ }
+
+ @After
+ fun teardown() {
+ testApp.exit(wmHelper)
+ virtualDisplay?.let {
+ it.release()
+ }
+ extendedDisplaySettingsSession.close()
+ }
+
+ companion object {
+ const val DISPLAY_NAME = "testVirtualDisplay"
+ const val DISPLAY_HEIGHT = 600
+ const val DISPLAY_WIDTH = 800
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt
new file mode 100644
index 000000000000..0b2aacd00aa6
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/utils/src/com/android/wm/shell/ExtendedDisplaySettingsSession.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell
+
+import android.content.ContentResolver
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+
+class ExtendedDisplaySettingsSession(private val contentResolver: ContentResolver) {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ fun open() {
+ Settings.Global.putInt(contentResolver, settingName, 1)
+ }
+
+ fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 417b43a9c6c0..22cc65d8ffaf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -34,7 +34,6 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView
-import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayImeController
import com.android.wm.shell.common.DisplayInsetsController
@@ -143,7 +142,7 @@ class BubbleViewInfoTest : ShellTestCase() {
mock<Transitions>(),
mock<SyncTransactionQueue>(),
mock<IWindowManager>(),
- mock<BubbleProperties>()
+ BubbleResizabilityChecker()
)
val bubbleStackViewManager = BubbleStackViewManager.fromBubbleController(bubbleController)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
new file mode 100644
index 000000000000..75ad621e1cad
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipDesktopStateTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import static com.android.window.flags.Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_PIP;
+import static com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.desktopmode.DesktopRepository;
+import com.android.wm.shell.desktopmode.DesktopUserRepositories;
+import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * Unit test against {@link PipDesktopState}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PIP)
+public class PipDesktopStateTest {
+ @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
+ @Mock private Optional<DesktopUserRepositories> mMockDesktopUserRepositoriesOptional;
+ @Mock private Optional<DesktopWallpaperActivityTokenProvider>
+ mMockDesktopWallpaperActivityTokenProviderOptional;
+ @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
+ @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
+ @Mock private DesktopRepository mMockDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mMockRootTaskDisplayAreaOrganizer;
+ @Mock private ActivityManager.RunningTaskInfo mMockTaskInfo;
+
+ private static final int DISPLAY_ID = 1;
+ private DisplayAreaInfo mDefaultTda;
+ private PipDesktopState mPipDesktopState;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockDesktopUserRepositoriesOptional.get()).thenReturn(mMockDesktopUserRepositories);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.get()).thenReturn(
+ mMockDesktopWallpaperActivityTokenProvider);
+ when(mMockDesktopUserRepositories.getCurrent()).thenReturn(mMockDesktopRepository);
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(true);
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(true);
+
+ when(mMockTaskInfo.getDisplayId()).thenReturn(DISPLAY_ID);
+ when(mMockPipDisplayLayoutState.getDisplayId()).thenReturn(DISPLAY_ID);
+
+ mDefaultTda = new DisplayAreaInfo(Mockito.mock(WindowContainerToken.class), DISPLAY_ID, 0);
+ when(mMockRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DISPLAY_ID)).thenReturn(
+ mDefaultTda);
+
+ mPipDesktopState = new PipDesktopState(mMockPipDisplayLayoutState,
+ mMockDesktopUserRepositoriesOptional,
+ mMockDesktopWallpaperActivityTokenProviderOptional,
+ mMockRootTaskDisplayAreaOrganizer);
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopRepositoryEmpty_returnsFalse() {
+ when(mMockDesktopUserRepositoriesOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ public void isDesktopWindowingPipEnabled_desktopWallpaperEmpty_returnsFalse() {
+ when(mMockDesktopWallpaperActivityTokenProviderOptional.isPresent()).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isDesktopWindowingPipEnabled());
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_CONNECTED_DISPLAYS_PIP)
+ public void isConnectedDisplaysPipEnabled_returnsTrue() {
+ assertTrue(mPipDesktopState.isConnectedDisplaysPipEnabled());
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipPresent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountNonzero_minimizedPipAbsent_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertTrue(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void isPipEnteringInDesktopMode_visibleCountZero_minimizedPipAbsent_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopRepository.isMinimizedPipPresentInDisplay(DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.isPipEnteringInDesktopMode(mMockTaskInfo));
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperInvisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(false);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountNonzero_wallpaperVisible_returnsFalse() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertFalse(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void shouldExitPipExitDesktopMode_visibleCountZero_wallpaperVisible_returnsTrue() {
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(0);
+ when(mMockDesktopWallpaperActivityTokenProvider.isWallpaperActivityVisible(
+ DISPLAY_ID)).thenReturn(true);
+
+ assertTrue(mPipDesktopState.shouldExitPipExitDesktopMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFreeform_returnsUndefined() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToDesktop_displayFullscreen_returnsFreeform() {
+ // Set visible task count to 1 so isPipExitingToDesktopMode returns true
+ when(mMockDesktopRepository.getVisibleTaskCount(DISPLAY_ID)).thenReturn(1);
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_FREEFORM, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ @Test
+ public void getOutPipWindowingMode_exitToFullscreen_displayFullscreen_returnsUndefined() {
+ setDisplayWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ assertEquals(WINDOWING_MODE_UNDEFINED, mPipDesktopState.getOutPipWindowingMode());
+ }
+
+ private void setDisplayWindowingMode(int windowingMode) {
+ mDefaultTda.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index fd3d3b5b6e2f..8c34c1946702 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -36,6 +36,7 @@ import androidx.test.annotation.UiThreadTest;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
@@ -66,9 +67,9 @@ public class DividerViewTest extends ShellTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
Configuration configuration = getConfiguration();
- mSplitLayout = new SplitLayout("TestSplitLayout", mContext, configuration,
+ mSplitLayout = spy(new SplitLayout("TestSplitLayout", mContext, configuration,
mSplitLayoutHandler, mCallbacks, mDisplayController, mDisplayImeController,
- mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler);
+ mTaskOrganizer, SplitLayout.PARALLAX_NONE, mSplitState, mHandler));
SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
mContext,
configuration, mCallbacks);
@@ -98,6 +99,14 @@ public class DividerViewTest extends ShellTestCase {
"false", false);
}
+ @Test
+ public void swapDividerActionForA11y() {
+ mDividerView.setAccessibilityDelegate(mDividerView.mHandleDelegate);
+ mDividerView.getAccessibilityDelegate().performAccessibilityAction(mDividerView,
+ R.id.action_swap_apps, null);
+ verify(mSplitLayout, times(1)).onDoubleTappedDivider();
+ }
+
private static MotionEvent getMotionEvent(long eventTime, int action, float x, float y) {
MotionEvent.PointerProperties properties = new MotionEvent.PointerProperties();
properties.id = 0;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
index 09ffd946ea19..d6b13610c9c1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopActivityOrientationChangeHandlerTest.kt
@@ -113,7 +113,7 @@ class DesktopActivityOrientationChangeHandlerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
index 470c110fd49b..403d468a7034 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeKeyGestureHandlerTest.kt
@@ -112,7 +112,7 @@ class DesktopModeKeyGestureHandlerTest : ShellTestCase() {
.strictness(Strictness.LENIENT)
.spyStatic(DesktopModeStatus::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index e46d2c7147ed..13b44977e9c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -19,22 +19,30 @@ package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
import android.graphics.PointF
import android.graphics.Rect
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
import android.view.SurfaceControl
import androidx.test.filters.SmallTest
import com.android.internal.policy.SystemBarUtils
+import com.android.modules.utils.testing.ExtendedMockitoRule
+import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE
import com.android.wm.shell.R
import com.android.wm.shell.RootTaskDisplayAreaOrganizer
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.google.common.truth.Truth.assertThat
import org.junit.Before
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
+import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
/**
@@ -43,9 +51,19 @@ import org.mockito.kotlin.whenever
* Usage: atest WMShellUnitTests:DesktopModeVisualIndicatorTest
*/
@SmallTest
+@RunWithLooper
@RunWith(AndroidTestingRunner::class)
+@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
class DesktopModeVisualIndicatorTest : ShellTestCase() {
- @Mock private lateinit var taskInfo: RunningTaskInfo
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ @JvmField
+ @Rule
+ val extendedMockitoRule =
+ ExtendedMockitoRule.Builder(this).mockStatic(DesktopModeStatus::class.java).build()!!
+
+ private lateinit var taskInfo: RunningTaskInfo
@Mock private lateinit var syncQueue: SyncTransactionQueue
@Mock private lateinit var displayController: DisplayController
@Mock private lateinit var taskSurface: SurfaceControl
@@ -56,10 +74,13 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
@Before
fun setUp() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(true)
whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
whenever(displayController.getDisplayLayout(anyInt())).thenReturn(displayLayout)
+ whenever(displayController.getDisplay(anyInt())).thenReturn(mContext.display)
+ taskInfo = DesktopTestHelpers.createFullscreenTask()
}
@Test
@@ -190,6 +211,39 @@ class DesktopModeVisualIndicatorTest : ShellTestCase() {
assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
}
+ @Test
+ @EnableFlags(
+ com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_TO_FULLSCREEN,
+ com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE,
+ )
+ fun testDefaultIndicatorWithNoDesktop() {
+ whenever(DesktopModeStatus.canEnterDesktopMode(any())).thenReturn(false)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+ var result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(-10000f, 500f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+
+ result = visualIndicator.updateIndicatorType(PointF(500f, 0f))
+ assertThat(result)
+ .isEqualTo(DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR)
+
+ createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+ result = visualIndicator.updateIndicatorType(PointF(500f, 500f))
+ assertThat(result).isEqualTo(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
+ }
+
private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
visualIndicator =
DesktopModeVisualIndicator(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 90f342f91a38..6a343c56d364 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -27,6 +27,7 @@ import androidx.test.filters.SmallTest
import com.android.window.flags.Flags
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE
import com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_PIP
+import com.android.window.flags.Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND
import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.ShellExecutor
@@ -35,6 +36,7 @@ import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.sysui.ShellInit
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.fail
+import kotlin.test.assertEquals
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -1076,13 +1078,37 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 1, isVisible = true)
repo.minimizeTask(displayId = DEFAULT_DISPLAY, taskId = 2)
- val tasksBeforeRemoval = repo.removeDesk(displayId = DEFAULT_DISPLAY)
+ val tasksBeforeRemoval = repo.removeDesk(deskId = DEFAULT_DISPLAY)
assertThat(tasksBeforeRemoval).containsExactly(1, 2, 3).inOrder()
assertThat(repo.getActiveTasks(displayId = DEFAULT_DISPLAY)).isEmpty()
}
@Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_active_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 3)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(3)
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleDesks_inactive_removes() {
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 2)
+ repo.addDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+ repo.setActiveDesk(displayId = DEFAULT_DISPLAY, deskId = 3)
+
+ repo.removeDesk(deskId = 2)
+
+ assertThat(repo.getDeskIds(displayId = DEFAULT_DISPLAY)).doesNotContain(2)
+ }
+
+ @Test
fun getTaskInFullImmersiveState_byDisplay() {
repo.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
repo.setActiveDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -1164,6 +1190,26 @@ class DesktopRepositoryTest(flags: FlagsParameterization) : ShellTestCase() {
assertThat(repo.getActiveTaskIdsInDesk(999)).contains(6)
}
+ @Test
+ @DisableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk() {
+ repo.addDesk(SECOND_DISPLAY, SECOND_DISPLAY)
+
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = SECOND_DISPLAY))
+ }
+
+ @Test
+ @EnableFlags(FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun getDisplayForDesk_multipleDesks() {
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 6)
+ repo.addDesk(DEFAULT_DISPLAY, deskId = 7)
+ repo.addDesk(SECOND_DISPLAY, deskId = 8)
+ repo.addDesk(SECOND_DISPLAY, deskId = 9)
+
+ assertEquals(DEFAULT_DISPLAY, repo.getDisplayForDesk(deskId = 7))
+ assertEquals(SECOND_DISPLAY, repo.getDisplayForDesk(deskId = 8))
+ }
+
class TestListener : DesktopRepository.ActiveTasksListener {
var activeChangesOnDefaultDisplay = 0
var activeChangesOnSecondaryDisplay = 0
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 05ecd5d4b72f..c10d2afbdc99 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -118,7 +118,9 @@ import com.android.wm.shell.desktopmode.ExitDesktopTaskTransitionHandler.FULLSCR
import com.android.wm.shell.desktopmode.common.ToggleTaskSizeInteraction
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
import com.android.wm.shell.desktopmode.minimize.DesktopWindowLimitRemoteHandler
+import com.android.wm.shell.desktopmode.multidesks.DeskTransition
import com.android.wm.shell.desktopmode.multidesks.DesksOrganizer
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.desktopmode.persistence.Desktop
import com.android.wm.shell.desktopmode.persistence.DesktopPersistentRepository
import com.android.wm.shell.desktopmode.persistence.DesktopRepositoryInitializer
@@ -180,6 +182,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argThat
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.capture
@@ -251,6 +254,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
private lateinit var overviewToDesktopTransitionObserver: OverviewToDesktopTransitionObserver
@Mock private lateinit var desksOrganizer: DesksOrganizer
@Mock private lateinit var userProfileContexts: UserProfileContexts
+ @Mock private lateinit var desksTransitionsObserver: DesksTransitionObserver
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
@@ -286,7 +290,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(Toast::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
testScope = CoroutineScope(Dispatchers.Unconfined + SupervisorJob())
shellInit = spy(ShellInit(testExecutor))
@@ -406,6 +410,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
Optional.of(bubbleController),
overviewToDesktopTransitionObserver,
desksOrganizer,
+ desksTransitionsObserver,
userProfileContexts,
desktopModeCompatPolicy,
)
@@ -536,7 +541,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_allAppsInvisible_bringsToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -725,7 +729,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_appsAlreadyVisible_bringsToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskVisible(task1)
@@ -764,8 +767,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersOnlyFreeformTasks() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
+ fun showDesktopApps_someAppsInvisible_reordersAll_desktopWallpaperEnabled() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
markTaskHidden(task1)
@@ -782,24 +784,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
wct.assertReorderAt(index = 2, task2)
}
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_someAppsInvisible_desktopWallpaperEnabled_reordersAll() {
- val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
- markTaskHidden(task1)
- markTaskVisible(task2)
-
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct =
- getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- assertThat(wct.hierarchyOps).hasSize(3)
- // Expect order to be from bottom: wallpaper intent, task1, task2
- wct.assertReorderAt(index = 0, wallpaperToken)
- wct.assertReorderAt(index = 1, task1)
- wct.assertReorderAt(index = 2, task2)
- }
-
@Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_noActiveTasks_reorderHomeToTop_desktopWallpaperDisabled() {
@@ -815,9 +799,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_addsDesktopWallpaper() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
-
+ fun showDesktopApps_noActiveTasks_addDesktopWallpaper_desktopWallpaperEnabled() {
controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
val wct =
@@ -826,16 +808,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
- fun showDesktopApps_noActiveTasks_desktopWallpaperEnabled_reordersDesktopWallpaper() {
- controller.showDesktopApps(DEFAULT_DISPLAY, RemoteTransition(TestRemoteTransition()))
-
- val wct =
- getLatestWct(type = TRANSIT_TO_FRONT, handlerClass = OneShotRemoteHandler::class.java)
- wct.assertReorderAt(index = 0, wallpaperToken)
- }
-
- @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperDisabled() {
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
@@ -859,7 +831,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_twoDisplays_bringsToFrontOnlyOneDisplay_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
taskRepository.addDesk(displayId = SECOND_DISPLAY, deskId = SECOND_DISPLAY)
val homeTaskDefaultDisplay = setUpHomeTask(DEFAULT_DISPLAY)
val taskDefaultDisplay = setUpFreeformTask(DEFAULT_DISPLAY)
@@ -904,7 +875,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun showDesktopApps_desktopWallpaperEnabled_dontReorderMinimizedTask() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val homeTask = setUpHomeTask()
val freeformTask = setUpFreeformTask()
val minimizedTask = setUpFreeformTask()
@@ -1370,7 +1340,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveTaskToDesktop_desktopWallpaperEnabled_nonRunningTask_launchesInFreeform() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createTaskInfo(1)
whenever(shellTaskOrganizer.getRunningTaskInfo(anyInt())).thenReturn(null)
whenever(recentTasksController.findTaskInBackground(anyInt())).thenReturn(task)
@@ -1553,7 +1522,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_otherFreeformTasksBroughtToFront_desktopWallpaperEnabled() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask = setUpFreeformTask()
val fullscreenTask = setUpFullscreenTask()
markTaskHidden(freeformTask)
@@ -1656,7 +1624,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun moveRunningTaskToDesktop_desktopWallpaperEnabled_bringsTasksOverLimit_dontShowBackTask() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTasks = (1..MAX_TASK_LIMIT).map { _ -> setUpFreeformTask() }
val newTask = setUpFullscreenTask()
val homeTask = setUpHomeTask()
@@ -2677,7 +2644,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_fullscreenTask_noTasks_enforceDesktop_freeformDisplay_returnFreeformWCT() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
whenever(DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(context)).thenReturn(true)
val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
@@ -2809,7 +2775,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_freeformNotVisible_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val freeformTask1 = setUpFreeformTask()
val freeformTask2 = createFreeformTask()
@@ -2844,9 +2809,7 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_desktopWallpaperEnabled_noOtherTasks_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val task = createFreeformTask()
-
val result = controller.handleRequest(Binder(), createTransition(task))
assertNotNull(result, "Should handle request")
@@ -2874,7 +2837,6 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
fun handleRequest_freeformTask_dskWallpaperEnabled_freeformOnOtherDisplayOnly_reorderedToTop() {
- whenever(desktopWallpaperActivityTokenProvider.getToken()).thenReturn(null)
val taskDefaultDisplay = createFreeformTask(displayId = DEFAULT_DISPLAY)
// Second display task
createFreeformTask(displayId = SECOND_DISPLAY)
@@ -3619,13 +3581,14 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasks_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasks_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(3)
@@ -3636,14 +3599,15 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
- fun removeDesktop_multipleTasksWithBackgroundTask_removesAll() {
+ @DisableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun removeDesk_multipleTasksWithBackgroundTask_removesAll() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
val task3 = setUpFreeformTask()
taskRepository.minimizeTask(DEFAULT_DISPLAY, task2.taskId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task3.taskId)).thenReturn(null)
- controller.removeDesktop(displayId = DEFAULT_DISPLAY)
+ controller.removeDefaultDeskInDisplay(displayId = DEFAULT_DISPLAY)
val wct = getLatestWct(TRANSIT_CLOSE)
assertThat(wct.hierarchyOps).hasSize(2)
@@ -3653,6 +3617,30 @@ class DesktopTasksControllerTest(flags: FlagsParameterization) : ShellTestCase()
}
@Test
+ @EnableFlags(
+ Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
+ Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND,
+ )
+ fun removeDesk_multipleDesks_addsPendingTransition() {
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_CLOSE), any(), anyOrNull()))
+ .thenReturn(transition)
+ taskRepository.addDesk(DEFAULT_DISPLAY, deskId = 2)
+
+ controller.removeDesk(deskId = 2)
+
+ verify(desksOrganizer).removeDesk(any(), eq(2))
+ verify(desksTransitionsObserver)
+ .addPendingTransition(
+ argThat {
+ this is DeskTransition.RemoveDesk &&
+ this.token == transition &&
+ this.deskId == 2
+ }
+ )
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
val spyController = spy(controller)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
index 091159c67db5..c29edece5537 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt
@@ -47,11 +47,13 @@ import com.android.wm.shell.back.BackAnimationController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG
import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider
+import com.android.wm.shell.desktopmode.multidesks.DesksTransitionObserver
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP
import com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
@@ -92,6 +94,7 @@ class DesktopTasksTransitionObserverTest {
private val backAnimationController = mock<BackAnimationController>()
private val desktopWallpaperActivityTokenProvider =
mock<DesktopWallpaperActivityTokenProvider>()
+ private val desksTransitionObserver = mock<DesksTransitionObserver>()
private val wallpaperToken = MockToken().token()
private lateinit var transitionObserver: DesktopTasksTransitionObserver
@@ -115,6 +118,7 @@ class DesktopTasksTransitionObserverTest {
mixedHandler,
backAnimationController,
desktopWallpaperActivityTokenProvider,
+ desksTransitionObserver,
shellInit,
)
}
@@ -411,6 +415,21 @@ class DesktopTasksTransitionObserverTest {
verify(taskRepository).setTaskInPip(task.displayId, task.taskId, enterPip = false)
}
+ @Test
+ fun onTransitionReady_forwardsToDesksTransitionObserver() {
+ val transition = Binder()
+ val info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0)
+
+ transitionObserver.onTransitionReady(
+ transition = transition,
+ info = info,
+ StubTransaction(),
+ StubTransaction(),
+ )
+
+ verify(desksTransitionObserver).onTransitionReady(transition, info)
+ }
+
private fun createBackNavigationTransition(
task: RunningTaskInfo?,
type: Int = TRANSIT_TO_BACK,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
new file mode 100644
index 000000000000..bfbaa84e9312
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/multidesks/DesksTransitionObserverTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.desktopmode.multidesks
+
+import android.os.Binder
+import android.platform.test.annotations.EnableFlags
+import android.platform.test.flag.junit.SetFlagsRule
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.WindowManager.TRANSIT_CLOSE
+import android.window.TransitionInfo
+import androidx.test.filters.SmallTest
+import com.android.window.flags.Flags
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopUserRepositories
+import com.android.wm.shell.sysui.ShellInit
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for [DesksTransitionObserver].
+ *
+ * Build/Install/Run: atest WMShellUnitTests:DesksTransitionObserverTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesksTransitionObserverTest : ShellTestCase() {
+
+ @JvmField @Rule val setFlagsRule = SetFlagsRule()
+
+ private lateinit var desktopUserRepositories: DesktopUserRepositories
+ private lateinit var observer: DesksTransitionObserver
+
+ private val repository: DesktopRepository
+ get() = desktopUserRepositories.current
+
+ @Before
+ fun setUp() {
+ desktopUserRepositories =
+ DesktopUserRepositories(
+ context,
+ ShellInit(TestShellExecutor()),
+ /* shellController= */ mock(),
+ /* persistentRepository= */ mock(),
+ /* repositoryInitializer= */ mock(),
+ /* mainCoroutineScope= */ mock(),
+ /* userManager= */ mock(),
+ )
+ observer = DesksTransitionObserver(desktopUserRepositories)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_removesFromRepository() {
+ val transition = Binder()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = null,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(repository.getDeskIds(DEFAULT_DISPLAY)).doesNotContain(5)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_MULTIPLE_DESKTOPS_BACKEND)
+ fun onTransitionReady_removeDesk_invokesOnRemoveListener() {
+ class FakeOnDeskRemovedListener : OnDeskRemovedListener {
+ var lastDeskRemoved: Int? = null
+
+ override fun onDeskRemoved(lastDisplayId: Int, deskId: Int) {
+ lastDeskRemoved = deskId
+ }
+ }
+ val transition = Binder()
+ val removeListener = FakeOnDeskRemovedListener()
+ val removeTransition =
+ DeskTransition.RemoveDesk(
+ transition,
+ displayId = DEFAULT_DISPLAY,
+ deskId = 5,
+ tasks = setOf(10, 11),
+ onDeskRemovedListener = removeListener,
+ )
+ repository.addDesk(DEFAULT_DISPLAY, deskId = 5)
+
+ observer.addPendingTransition(removeTransition)
+ observer.onTransitionReady(
+ transition = transition,
+ info = TransitionInfo(TRANSIT_CLOSE, /* flags= */ 0),
+ )
+
+ assertThat(removeListener.lastDeskRemoved).isEqualTo(5)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
index bd857c7dcd45..8e0381e4f933 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipSchedulerTest.java
@@ -26,7 +26,6 @@ import static org.mockito.kotlin.MatchersKt.eq;
import static org.mockito.kotlin.VerificationKt.times;
import static org.mockito.kotlin.VerificationKt.verify;
-import android.app.TaskInfo;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Matrix;
@@ -39,12 +38,9 @@ import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.pip.PipBoundsState;
-import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.desktopmode.DesktopUserRepositories;
-import com.android.wm.shell.desktopmode.desktopwallpaperactivity.DesktopWallpaperActivityTokenProvider;
+import com.android.wm.shell.common.pip.PipDesktopState;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip2.animation.PipAlphaAnimator;
@@ -55,11 +51,8 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
/**
* Unit test against {@link PipScheduler}
*/
@@ -77,16 +70,13 @@ public class PipSchedulerTest {
@Mock private PipBoundsState mMockPipBoundsState;
@Mock private ShellExecutor mMockMainExecutor;
@Mock private PipTransitionState mMockPipTransitionState;
+ @Mock private PipDesktopState mMockPipDesktopState;
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private Runnable mMockUpdateMovementBoundsRunnable;
@Mock private WindowContainerToken mMockPipTaskToken;
@Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
@Mock private SurfaceControl.Transaction mMockTransaction;
@Mock private PipAlphaAnimator mMockAlphaAnimator;
- @Mock private DesktopUserRepositories mMockDesktopUserRepositories;
- @Mock private DesktopWallpaperActivityTokenProvider mMockDesktopWallpaperActivityTokenProvider;
- @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
-
@Captor private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Captor private ArgumentCaptor<WindowContainerTransaction> mWctArgumentCaptor;
@@ -101,14 +91,9 @@ public class PipSchedulerTest {
when(mMockFactory.getTransaction()).thenReturn(mMockTransaction);
when(mMockTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
.thenReturn(mMockTransaction);
- when(mMockDesktopUserRepositories.getCurrent())
- .thenReturn(Mockito.mock(DesktopRepository.class));
- when(mMockPipTransitionState.getPipTaskInfo()).thenReturn(Mockito.mock(TaskInfo.class));
mPipScheduler = new PipScheduler(mMockContext, mMockPipBoundsState, mMockMainExecutor,
- mMockPipTransitionState, Optional.of(mMockDesktopUserRepositories),
- Optional.of(mMockDesktopWallpaperActivityTokenProvider),
- mRootTaskDisplayAreaOrganizer);
+ mMockPipTransitionState, mMockPipDesktopState);
mPipScheduler.setPipTransitionController(mMockPipTransitionController);
mPipScheduler.setSurfaceControlTransactionFactory(mMockFactory);
mPipScheduler.setPipAlphaAnimatorSupplier((context, leash, startTx, finishTx, direction) ->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
index 3923a1ef5633..1b462c30e017 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTaskListenerTest.java
@@ -165,6 +165,25 @@ public class PipTaskListenerTest {
}
@Test
+ public void onTaskInfoChanged_withNullPipParams_doNothing() {
+ mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
+ mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
+ mMockPipBoundsAlgorithm, mMockShellExecutor);
+ mPipTaskListener.addParamsChangedListener(mMockPipParamsChangedCallback);
+ Rational aspectRatio = new Rational(4, 3);
+ when(mMockPipBoundsState.getAspectRatio()).thenReturn(aspectRatio.toFloat());
+ String action1 = "action1";
+ mPipTaskListener.onTaskInfoChanged(getTaskInfo(aspectRatio, action1));
+
+ clearInvocations(mMockPipParamsChangedCallback);
+ mPipTaskListener.onTaskInfoChanged(new ActivityManager.RunningTaskInfo());
+
+ verifyZeroInteractions(mMockPipParamsChangedCallback);
+ verify(mMockPipTransitionState, times(0))
+ .setOnIdlePipTransitionStateRunnable(any(Runnable.class));
+ }
+
+ @Test
public void onTaskInfoChanged_withActionsChanged_callbackActionsChanged() {
mPipTaskListener = new PipTaskListener(mMockContext, mMockShellTaskOrganizer,
mMockPipTransitionState, mMockPipScheduler, mMockPipBoundsState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
index 4dac99b14aaf..33f14acd0f02 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatusTest.kt
@@ -39,6 +39,9 @@ import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+/**
+ * Test class for [DesktopModeStatus].
+ */
@SmallTest
@Presubmit
@EnableFlags(Flags.FLAG_SHOW_DESKTOP_WINDOWING_DEV_OPTION)
@@ -56,6 +59,7 @@ class DesktopModeStatusTest : ShellTestCase() {
doReturn(false).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
+ setDeviceEligibleForDesktopMode(false)
doReturn(context.contentResolver).whenever(mockContext).contentResolver
resetDesktopModeFlagsCache()
resetEnforceDeviceRestriction()
@@ -74,7 +78,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configsOff_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_returnsFalse() {
assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
}
@@ -83,8 +87,8 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configsOn_disableDeviceRestrictions_returnsFalse() {
- doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
+ fun canEnterDesktopMode_DWFlagDisabled_deviceEligible_configDevOptionOn_returnsFalse() {
+ setDeviceEligibleForDesktopMode(true)
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -98,7 +102,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_configDevOptionOn_returnsFalse() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -111,7 +115,7 @@ class DesktopModeStatusTest : ShellTestCase() {
Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION
)
@Test
- fun canEnterDesktopMode_DWFlagDisabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ fun canEnterDesktopMode_DWFlagDisabled_deviceNotEligible_forceUsingDevOption_returnsTrue() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -123,14 +127,7 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configsOff_returnsFalse() {
- assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isFalse()
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Test
- fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOff_returnsFalse() {
+ fun canEnterDesktopMode_DWFlagEnabled_deviceNotEligible_returnsFalse() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -141,17 +138,8 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configDesktopModeOn_returnsTrue() {
- doReturn(true).whenever(mockResources).getBoolean(eq(R.bool.config_isDesktopModeSupported))
-
- assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
- }
-
- @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- @Test
- fun canEnterDesktopMode_DWFlagEnabled_configsOff_disableDeviceRestrictions_returnsTrue() {
- disableEnforceDeviceRestriction()
+ fun canEnterDesktopMode_DWFlagEnabled_deviceEligible_returnsTrue() {
+ setDeviceEligibleForDesktopMode(true)
assertThat(DesktopModeStatus.canEnterDesktopMode(mockContext)).isTrue()
}
@@ -159,7 +147,7 @@ class DesktopModeStatusTest : ShellTestCase() {
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_MODE_THROUGH_DEV_OPTION)
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
@Test
- fun canEnterDesktopMode_DWFlagEnabled_configDevOptionOn_flagOverrideOn_returnsTrue() {
+ fun canEnterDesktopMode_DWFlagEnabled_deviceNotEligible_forceUsingDevOption_returnsTrue() {
doReturn(true).whenever(mockResources).getBoolean(
eq(R.bool.config_isDesktopModeDevOptionSupported)
)
@@ -198,6 +186,28 @@ class DesktopModeStatusTest : ShellTestCase() {
assertThat(DesktopModeStatus.isDeviceEligibleForDesktopMode(mockContext)).isTrue()
}
+ @DisableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagDisabled_returnsFalse() {
+ setDeviceEligibleForDesktopMode(true)
+
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagEnabled_deviceNotEligible_returnsFalse() {
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isFalse()
+ }
+
+ @EnableFlags(Flags.FLAG_SHOW_DESKTOP_EXPERIENCE_DEV_OPTION)
+ @Test
+ fun canShowDesktopExperienceDevOption_flagEnabled_deviceEligible_returnsTrue() {
+ setDeviceEligibleForDesktopMode(true)
+
+ assertThat(DesktopModeStatus.canShowDesktopExperienceDevOption(mockContext)).isTrue()
+ }
+
private fun resetEnforceDeviceRestriction() {
setEnforceDeviceRestriction(true)
}
@@ -232,4 +242,10 @@ class DesktopModeStatusTest : ShellTestCase() {
DEVELOPMENT_OVERRIDE_DESKTOP_MODE_FEATURES, override.setting
)
}
+
+ private fun setDeviceEligibleForDesktopMode(eligible: Boolean) {
+ val deviceRestrictions = DesktopModeStatus::class.java.getDeclaredField("ENFORCE_DEVICE_RESTRICTIONS")
+ deviceRestrictions.isAccessible = true
+ deviceRestrictions.setBoolean(/* obj= */ null, /* z= */ !eligible)
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
index 8b4cf6d1fabe..e40d97c68554 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelAppHandleOnlyTest.kt
@@ -67,7 +67,7 @@ class DesktopModeWindowDecorViewModelAppHandleOnlyTest :
.spyStatic(DesktopModeStatus::class.java)
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(false).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
doReturn(true).`when` { DesktopModeStatus.overridesShowAppHandle(any())}
setUpCommon()
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 737780ed8024..4a91fb429f7b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -116,7 +116,7 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
.spyStatic(DragPositioningCallbackUtility::class.java)
.startMocking()
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(Mockito.any()) }
doReturn(false).`when` { DesktopModeStatus.overridesShowAppHandle(Mockito.any()) }
setUpCommon()
@@ -379,37 +379,21 @@ class DesktopModeWindowDecorViewModelTests : DesktopModeWindowDecorViewModelTest
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun testWindowDecor_desktopModeUnsupportedOnDevice_deviceRestrictionsOverridden_decorCreated() {
- // Simulate enforce device restrictions system property overridden to false
- whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(false)
- // Simulate device that doesn't support desktop mode
- doReturn(false).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
-
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
- fun testWindowDecor_deviceSupportsDesktopMode_decorCreated() {
+ fun testWindowDecor_deviceEligibleForDesktopMode_decorCreated() {
// Simulate default enforce device restrictions system property
whenever(DesktopModeStatus.enforceDeviceRestrictions()).thenReturn(true)
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
- doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(any()) }
+ doReturn(true).`when` { DesktopModeStatus.isDeviceEligibleForDesktopMode(any()) }
setUpMockDecorationsForTasks(task)
onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
+ assertTrue(task.taskId in windowDecorByTaskIdSpy)
}
@Test
fun testOnDecorMaximizedOrRestored_togglesTaskSize_maximize() {
- val maxOrRestoreListenerCaptor = forClass(Function0::class.java)
- as ArgumentCaptor<Function0<Unit>>
+ val maxOrRestoreListenerCaptor = forClass(Function0::class.java as Class<Function0<Unit>>)
val decor = createOpenTaskDecoration(
windowingMode = WINDOWING_MODE_FREEFORM,
onMaxOrRestoreListenerCaptor = maxOrRestoreListenerCaptor
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 12b1dd794a03..3dc53c5051e9 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -288,6 +288,7 @@ cc_benchmark {
"tests/AttributeResolution_bench.cpp",
"tests/CursorWindow_bench.cpp",
"tests/Generic_bench.cpp",
+ "tests/LocaleDataLookup_bench.cpp",
"tests/SparseEntry_bench.cpp",
"tests/Theme_bench.cpp",
],
diff --git a/libs/androidfw/LocaleDataLookup.cpp b/libs/androidfw/LocaleDataLookup.cpp
index 6e751a77f355..ea9e9a2d4280 100644
--- a/libs/androidfw/LocaleDataLookup.cpp
+++ b/libs/androidfw/LocaleDataLookup.cpp
@@ -7518,6 +7518,13 @@ const char* lookupLikelyScript(uint32_t packed_lang_region) {
}
}
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */
bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {
const uint64_t packed_locale =
((static_cast<uint64_t>(language_and_region)) << 32u) |
diff --git a/libs/androidfw/TypeWrappers.cpp b/libs/androidfw/TypeWrappers.cpp
index 970463492c1a..2a20106b6ee1 100644
--- a/libs/androidfw/TypeWrappers.cpp
+++ b/libs/androidfw/TypeWrappers.cpp
@@ -18,8 +18,11 @@
namespace android {
-TypeVariant::TypeVariant(const ResTable_type* data) : data(data), mLength(dtohl(data->entryCount)) {
- if (data->flags & ResTable_type::FLAG_SPARSE) {
+TypeVariant::TypeVariant(const ResTable_type* data)
+ : data(data)
+ , mLength(dtohl(data->entryCount))
+ , mSparse(data->flags & ResTable_type::FLAG_SPARSE) {
+ if (mSparse) {
const uint32_t entryCount = dtohl(data->entryCount);
const uintptr_t containerEnd = reinterpret_cast<uintptr_t>(data) + dtohl(data->header.size);
const uint32_t* const entryIndices = reinterpret_cast<const uint32_t*>(
@@ -40,18 +43,18 @@ TypeVariant::iterator& TypeVariant::iterator::operator++() {
mIndex = mTypeVariant->mLength;
}
- const ResTable_type* type = mTypeVariant->data;
- if ((type->flags & ResTable_type::FLAG_SPARSE) == 0) {
+ if (!mTypeVariant->mSparse) {
return *this;
}
// Need to adjust |mSparseIndex| as well if we've passed its current element.
+ const ResTable_type* type = mTypeVariant->data;
const uint32_t entryCount = dtohl(type->entryCount);
- const auto entryIndices = reinterpret_cast<const uint32_t*>(
- reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
if (mSparseIndex >= entryCount) {
return *this; // done
}
+ const auto entryIndices = reinterpret_cast<const uint32_t*>(
+ reinterpret_cast<uintptr_t>(type) + dtohs(type->header.headerSize));
const auto element = (const ResTable_sparseTypeEntry*)(entryIndices + mSparseIndex);
if (mIndex > dtohs(element->idx)) {
++mSparseIndex;
@@ -79,7 +82,7 @@ const ResTable_entry* TypeVariant::iterator::operator*() const {
}
uint32_t entryOffset;
- if (type->flags & ResTable_type::FLAG_SPARSE) {
+ if (mTypeVariant->mSparse) {
if (mSparseIndex >= entryCount) {
return nullptr;
}
diff --git a/libs/androidfw/include/androidfw/TypeWrappers.h b/libs/androidfw/include/androidfw/TypeWrappers.h
index db641b78a4e4..d901af3c908b 100644
--- a/libs/androidfw/include/androidfw/TypeWrappers.h
+++ b/libs/androidfw/include/androidfw/TypeWrappers.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef __TYPE_WRAPPERS_H
-#define __TYPE_WRAPPERS_H
+#pragma once
#include <androidfw/ResourceTypes.h>
#include <utils/ByteOrder.h>
@@ -54,7 +53,7 @@ struct TypeVariant {
enum class Kind { Begin, End };
iterator(const TypeVariant* tv, Kind kind)
: mTypeVariant(tv) {
- mSparseIndex = mIndex = kind == Kind::Begin ? 0 : tv->mLength;
+ mSparseIndex = mIndex = (kind == Kind::Begin ? 0 : tv->mLength);
// mSparseIndex here is technically past the number of sparse entries, but it is still
// ok as it is enough to infer that this is the end iterator.
}
@@ -75,9 +74,11 @@ struct TypeVariant {
const ResTable_type* data;
private:
- size_t mLength;
+ // For a dense table, this is the number of the elements.
+ // For a sparse table, this is the index of the last element + 1.
+ // In both cases, it can be used for iteration as the upper loop bound as in |i < mLength|.
+ uint32_t mLength;
+ bool mSparse;
};
} // namespace android
-
-#endif // __TYPE_WRAPPERS_H
diff --git a/libs/androidfw/tests/LocaleDataLookup_bench.cpp b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
new file mode 100644
index 000000000000..60ce3b944551
--- /dev/null
+++ b/libs/androidfw/tests/LocaleDataLookup_bench.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#include "benchmark/benchmark.h"
+
+#include "androidfw/LocaleDataLookup.h"
+
+namespace android {
+
+static void BM_LocaleDataLookupIsLocaleRepresentative(benchmark::State& state) {
+ for (auto&& _ : state) {
+ isLocaleRepresentative(packLocale("en", "US"), "Latn");
+ isLocaleRepresentative(packLocale("es", "ES"), "Latn");
+ isLocaleRepresentative(packLocale("zh", "CN"), "Hans");
+ isLocaleRepresentative(packLocale("pt", "BR"), "Latn");
+ isLocaleRepresentative(packLocale("ar", "EG"), "Arab");
+ isLocaleRepresentative(packLocale("hi", "IN"), "Deva");
+ isLocaleRepresentative(packLocale("jp", "JP"), "Jpan");
+ }
+}
+BENCHMARK(BM_LocaleDataLookupIsLocaleRepresentative);
+
+static void BM_LocaleDataLookupLikelyScript(benchmark::State& state) {
+ for (auto&& _ : state) {
+ lookupLikelyScript(packLocale("en", ""));
+ lookupLikelyScript(packLocale("es", ""));
+ lookupLikelyScript(packLocale("zh", ""));
+ lookupLikelyScript(packLocale("pt", ""));
+ lookupLikelyScript(packLocale("ar", ""));
+ lookupLikelyScript(packLocale("hi", ""));
+ lookupLikelyScript(packLocale("jp", ""));
+ lookupLikelyScript(packLocale("en", "US"));
+ lookupLikelyScript(packLocale("es", "ES"));
+ lookupLikelyScript(packLocale("zh", "CN"));
+ lookupLikelyScript(packLocale("pt", "BR"));
+ lookupLikelyScript(packLocale("ar", "EG"));
+ lookupLikelyScript(packLocale("hi", "IN"));
+ lookupLikelyScript(packLocale("jp", "JP"));
+ }
+}
+BENCHMARK(BM_LocaleDataLookupLikelyScript);
+
+
+} // namespace android
diff --git a/libs/androidfw/tests/TypeWrappers_test.cpp b/libs/androidfw/tests/TypeWrappers_test.cpp
index d66e05805484..69c24c5d8956 100644
--- a/libs/androidfw/tests/TypeWrappers_test.cpp
+++ b/libs/androidfw/tests/TypeWrappers_test.cpp
@@ -121,6 +121,7 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
values.push_back(std::nullopt);
values.push_back(std::nullopt);
values.push_back(Res_value{ sizeof(Res_value), 0, Res_value::TYPE_STRING, 0x87654321});
+ values.push_back(std::nullopt);
// test for combinations of compact_entry and short_offsets
for (size_t i = 0; i < 8; i++) {
@@ -191,6 +192,17 @@ TEST(TypeVariantIteratorTest, shouldIterateOverTypeWithoutErrors) {
++iter;
+ ASSERT_EQ(uint32_t(9), iter.index());
+ ASSERT_TRUE(NULL == *iter);
+ if (sparse) {
+ // Sparse iterator doesn't know anything beyond the last entry.
+ ASSERT_EQ(v.endEntries(), iter);
+ } else {
+ ASSERT_NE(v.endEntries(), iter);
+ }
+
+ ++iter;
+
ASSERT_EQ(v.endEntries(), iter);
}
}
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 1a8437108b72..fb1b5b57cce6 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -3888,7 +3888,9 @@ final public class MediaCodec {
/**
* Set a hardware graphic buffer to this queue request. Exactly one buffer must
- * be set for a queue request before calling {@link #queue}.
+ * be set for a queue request before calling {@link #queue}. Ownership of the
+ * hardware buffer is not transferred to this queue request, nor will it be transferred
+ * to the codec once {@link #queue} is called.
* <p>
* Note: buffers should have format {@link HardwareBuffer#YCBCR_420_888},
* a single layer, and an appropriate usage ({@link HardwareBuffer#USAGE_CPU_READ_OFTEN}
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index 87bb6eab6c4d..d27d7fc72a38 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -649,9 +649,19 @@ public final class RoutingSessionInfo implements Parcelable {
* @param sessionInfo the existing instance to copy data from.
*/
public Builder(@NonNull RoutingSessionInfo sessionInfo) {
+ this(sessionInfo, sessionInfo.getOriginalId());
+ }
+
+ /**
+ * Builds upon the given {@code sessionInfo}, using the given {@link #getOriginalId()} for
+ * the id.
+ *
+ * @hide
+ */
+ public Builder(@NonNull RoutingSessionInfo sessionInfo, String originalId) {
Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- mOriginalId = sessionInfo.mOriginalId;
+ mOriginalId = originalId;
mName = sessionInfo.mName;
mClientPackageName = sessionInfo.mClientPackageName;
mProviderId = sessionInfo.mProviderId;
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/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..428997e0c446 100644
--- a/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/module/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -51,7 +52,8 @@ public final class InferenceInfo implements Parcelable {
private final long endTimeMs;
/**
- * Suspended time in milliseconds.
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@ public final class InferenceInfo implements Parcelable {
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
index cae8db27a435..64524fb096cb 100644
--- a/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
+++ b/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/InferenceInfo.java
@@ -19,6 +19,7 @@ package android.app.ondeviceintelligence;
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE_MODULE;
import android.annotation.CurrentTimeMillisLong;
+import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.SystemApi;
@@ -50,8 +51,9 @@ public final class InferenceInfo implements Parcelable {
*/
private final long endTimeMs;
- /**
- * Suspended time in milliseconds.
+ /**
+ * The total duration of the period(s) during which the inference was
+ * suspended (i.e. not running), in milliseconds.
*/
private final long suspendedTimeMs;
@@ -61,7 +63,7 @@ public final class InferenceInfo implements Parcelable {
* @param uid Uid for the caller app.
* @param startTimeMs Inference start time (milliseconds from the epoch time).
* @param endTimeMs Inference end time (milliseconds from the epoch time).
- * @param suspendedTimeMs Suspended time in milliseconds.
+ * @param suspendedTimeMs Suspended duration, in milliseconds.
*/
InferenceInfo(int uid, long startTimeMs, long endTimeMs,
long suspendedTimeMs) {
@@ -128,11 +130,12 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Returns the suspended time in milliseconds.
+ * Returns the suspended duration, in milliseconds.
*
- * @return the suspended time in milliseconds.
+ * @return the total duration of the period(s) during which the inference
+ * was suspended (i.e. not running), in milliseconds.
*/
- @CurrentTimeMillisLong
+ @DurationMillisLong
public long getSuspendedTimeMillis() {
return suspendedTimeMs;
}
@@ -197,12 +200,14 @@ public final class InferenceInfo implements Parcelable {
}
/**
- * Sets the suspended time in milliseconds.
+ * Sets the suspended duration, in milliseconds.
*
- * @param suspendedTimeMs the suspended time in milliseconds.
+ * @param suspendedTimeMs the total duration of the period(s) in which
+ * the request was suspended (i.e. not running),
+ * in milliseconds.
* @return the Builder instance.
*/
- public @NonNull Builder setSuspendedTimeMillis(@CurrentTimeMillisLong long suspendedTimeMs) {
+ public @NonNull Builder setSuspendedTimeMillis(@DurationMillisLong long suspendedTimeMs) {
this.suspendedTimeMs = suspendedTimeMs;
return this;
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
index a140eb8424a8..a8483308556d 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt
@@ -35,6 +35,7 @@ interface BlockedByAdmin : RestrictedMode {
interface BlockedByEcm : RestrictedMode {
fun showRestrictedSettingsDetails()
+ fun isBlockedByPhoneCall() = false
}
internal data class BlockedByAdminImpl(
@@ -72,8 +73,13 @@ internal data class BlockedByEcmImpl(
private val context: Context,
private val intent: Intent,
) : BlockedByEcm {
+ private val reasonPhoneState = "phone_state"
override fun showRestrictedSettingsDetails() {
context.startActivity(intent)
}
+
+ override fun isBlockedByPhoneCall(): Boolean {
+ return intent.getStringExtra(Intent.EXTRA_REASON) == reasonPhoneState
+ }
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
index 0bb92ce72595..fb4880f10d3e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt
@@ -163,9 +163,11 @@ internal class RestrictedSwitchPreferenceModel(
is BlockedByAdmin ->
restrictedMode.getSummary(checkedIfBlockedByAdmin ?: checkedIfNoRestricted())
- is BlockedByEcm ->
+ is BlockedByEcm -> if (restrictedMode.isBlockedByPhoneCall()) {
+ context.getString(com.android.settingslib.R.string.disabled_in_phone_call_text)
+ } else {
context.getString(com.android.settingslib.R.string.disabled)
-
+ }
null -> context.getPlaceholder()
}
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 3da2271431f8..a3e42f1d1e51 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1228,6 +1228,8 @@
<!-- Summary for settings preference disabled by app ops [CHAR LIMIT=50] -->
<string name="disabled_by_app_ops_text">Controlled by Restricted Setting</string>
+ <!-- Summary for settings preference disabled while the device is in a phone call [CHAR LIMIT=50] -->
+ <string name="disabled_in_phone_call_text">Unavailable during calls</string>
<!-- [CHAR LIMIT=25] Manage applications, text telling using an application is disabled. -->
<string name="disabled">Disabled</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 212e43aa4044..1044750bae25 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -44,6 +44,8 @@ import androidx.preference.PreferenceViewHolder;
public class RestrictedPreferenceHelper {
private static final String TAG = "RestrictedPreferenceHelper";
+ private static final String REASON_PHONE_STATE = "phone_state";
+
private final Context mContext;
private final Preference mPreference;
String packageName;
@@ -121,7 +123,7 @@ public class RestrictedPreferenceHelper {
if (mDisabledByAdmin) {
summaryView.setText(disabledText);
} else if (mDisabledByEcm) {
- summaryView.setText(R.string.disabled_by_app_ops_text);
+ summaryView.setText(getEcmTextResId());
} else if (TextUtils.equals(disabledText, summaryView.getText())) {
// It's previously set to disabled text, clear it.
summaryView.setText(null);
@@ -323,7 +325,16 @@ public class RestrictedPreferenceHelper {
}
if (!isEnabled && mDisabledByEcm) {
- mPreference.setSummary(R.string.disabled_by_app_ops_text);
+ mPreference.setSummary(getEcmTextResId());
+ }
+ }
+
+ private int getEcmTextResId() {
+ if (mDisabledByEcmIntent != null && REASON_PHONE_STATE.equals(
+ mDisabledByEcmIntent.getStringExtra(Intent.EXTRA_REASON))) {
+ return R.string.disabled_in_phone_call_text;
+ } else {
+ return R.string.disabled_by_app_ops_text;
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
index 64a2de5025de..ecea5fd35150 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/TestModeBuilder.java
@@ -33,10 +33,12 @@ import android.service.notification.ZenPolicy;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
-import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
public class TestModeBuilder {
+ private static final AtomicInteger sNextId = new AtomicInteger(0);
+
private String mId;
private AutomaticZenRule mRule;
private ZenModeConfig.ZenRule mConfigZenRule;
@@ -47,7 +49,7 @@ public class TestModeBuilder {
public TestModeBuilder() {
// Reasonable defaults
- int id = new Random().nextInt(1000);
+ int id = sNextId.incrementAndGet();
mId = "rule_" + id;
mRule = new AutomaticZenRule.Builder("Test Rule #" + id, Uri.parse("rule://" + id))
.setPackage("some_package")
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 9ab8aa8898a3..8fe3a0c4b4ae 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -997,6 +997,12 @@
<uses-permission android:name="android.permission.health.READ_SKIN_TEMPERATURE"
android:featureFlag="android.permission.flags.replace_body_sensor_permission_enabled"/>
+ <!-- Permissions required for CTS test - CtsHealthFitnessDeviceTestCases-->
+ <uses-permission android:name="android.permission.BACKUP_HEALTH_CONNECT_DATA_AND_SETTINGS"
+ android:featureFlag="android.permission.flags.health_connect_backup_restore_permission_enabled"/>
+ <uses-permission android:name="android.permission.RESTORE_HEALTH_CONNECT_DATA_AND_SETTINGS"
+ android:featureFlag="android.permission.flags.health_connect_backup_restore_permission_enabled"/>
+
<!-- Permission for TestClassifier tests to get access to classifier by type -->
<uses-permission android:name="android.permission.ACCESS_TEXT_CLASSIFIER_BY_TYPE"
android:featureFlag="android.permission.flags.text_classifier_choice_api_enabled"/>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index bb0d5d7755cf..ac53dcb1982a 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -1697,13 +1697,6 @@ flag {
}
flag {
- name: "magic_portrait_wallpapers"
- namespace: "systemui"
- description: "Magic Portrait related changes in systemui"
- bug: "370863642"
-}
-
-flag {
name: "notes_role_qs_tile"
namespace: "systemui"
description: "Enables notes role qs tile which opens default notes role app in app bubbles"
@@ -2020,3 +2013,10 @@ flag {
description: "Show a Locked by your watch indicator on the keyguard when the device is locked by the watch."
bug: "387322459"
}
+
+flag {
+ name: "decouple_view_controller_in_animlib"
+ namespace: "systemui"
+ description: "Decouple view and controller in AnimLib."
+ bug: "393241010"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
index 103a9b5cf5f4..c5d2802c8941 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/ExpandableController.kt
@@ -319,7 +319,7 @@ internal class ExpandableControllerImpl(
override fun onTransitionAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onTransitionAnimationStart(isExpandingFullyAbove)
- overlay.value = composeViewRoot.rootView.overlay as ViewGroupOverlay
+ overlay.value = transitionContainer.overlay as ViewGroupOverlay
cujType?.let { InteractionJankMonitor.getInstance().begin(composeViewRoot, it) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
index 4a5ad6554dc6..b254963cc5e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
@@ -17,11 +17,13 @@
package com.android.systemui.common.ui.compose
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import com.android.systemui.common.shared.model.ContentDescription
/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
+@ReadOnlyComposable
fun ContentDescription.load(): String? {
return when (this) {
is ContentDescription.Loaded -> description
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
index 82d14369f239..8b0c00535262 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
@@ -21,9 +21,8 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.res.painterResource
-import androidx.core.graphics.drawable.toBitmap
+import com.android.compose.ui.graphics.painter.rememberDrawablePainter
import com.android.systemui.common.shared.model.Icon
/**
@@ -36,7 +35,7 @@ fun Icon(icon: Icon, modifier: Modifier = Modifier, tint: Color = LocalContentCo
val contentDescription = icon.contentDescription?.load()
when (icon) {
is Icon.Loaded -> {
- Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
+ Icon(rememberDrawablePainter(icon.drawable), contentDescription, modifier, tint)
}
is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
index 4e8121f14e4b..19adba0f39de 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/TextExt.kt
@@ -19,6 +19,7 @@ package com.android.systemui.common.ui.compose
import android.content.Context
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import com.android.systemui.common.shared.model.Text
@@ -26,6 +27,7 @@ import com.android.systemui.common.shared.model.Text.Companion.loadText
/** Returns the loaded [String] or `null` if there isn't one. */
@Composable
+@ReadOnlyComposable
fun Text.load(): String? {
return when (this) {
is Text.Loaded -> text
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d3417022565b..fa5e8aceef1d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -109,15 +109,22 @@ constructor(
}
if (isShadeLayoutWide && !isBypassEnabled) {
with(notificationSection) {
- Notifications(
- areNotificationsVisible = areNotificationsVisible,
- isShadeLayoutWide = true,
- burnInParams = null,
- modifier =
- Modifier.fillMaxWidth(0.5f)
- .fillMaxHeight()
- .align(alignment = Alignment.TopEnd),
- )
+ Box(modifier = Modifier.fillMaxHeight()) {
+ AodPromotedNotificationArea(
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .align(alignment = Alignment.TopStart)
+ )
+ Notifications(
+ areNotificationsVisible = areNotificationsVisible,
+ isShadeLayoutWide = true,
+ burnInParams = null,
+ modifier =
+ Modifier.fillMaxWidth(0.5f)
+ .fillMaxHeight()
+ .align(alignment = Alignment.TopEnd),
+ )
+ }
}
}
}
@@ -142,9 +149,18 @@ constructor(
}
} else {
Column {
- AodPromotedNotificationArea()
+ if (!isShadeLayoutWide) {
+ AodPromotedNotificationArea()
+ }
AodNotificationIcons(
- modifier = Modifier.padding(start = aodIconPadding)
+ modifier =
+ Modifier.padding(
+ top =
+ dimensionResource(
+ R.dimen.keyguard_status_view_bottom_margin
+ ),
+ start = aodIconPadding,
+ )
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index b4c60037b426..73b0750f4a54 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -17,11 +17,8 @@
package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
-import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
-import com.android.compose.animation.scene.UserActionDistanceScope
import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.notifications.ui.composable.NotificationsShade
@@ -31,9 +28,6 @@ import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- distance = UserActionDistance { _, shadeContentKey, _ ->
- calculateShadePanelTargetPositionY(shadeContentKey)
- }
// Ensure the clock isn't clipped by the shade outline during the transition from lockscreen.
sharedElement(
@@ -50,12 +44,4 @@ fun TransitionBuilder.toNotificationsShadeTransition(durationScale: Double = 1.0
fractionRange(start = .5f) { fade(Notifications.Elements.NotificationScrim) }
}
-/** Returns the Y position of the bottom of the shade container panel within [shadeOverlayKey]. */
-fun UserActionDistanceScope.calculateShadePanelTargetPositionY(shadeOverlayKey: ContentKey): Float {
- val marginTop = OverlayShade.Elements.Panel.targetOffset(shadeOverlayKey)?.y ?: 0f
- val panelHeight =
- OverlayShade.Elements.Panel.targetSize(shadeOverlayKey)?.height?.toFloat() ?: 0f
- return marginTop + panelHeight
-}
-
private val DefaultDuration = 300.milliseconds
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index c9fbb4da9ffb..43aa35854542 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -19,18 +19,13 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.compose.animation.scene.UserActionDistance
import com.android.systemui.shade.ui.composable.OverlayShade
import kotlin.time.Duration.Companion.milliseconds
fun TransitionBuilder.toQuickSettingsShadeTransition(durationScale: Double = 1.0) {
spec = tween(durationMillis = (DefaultDuration * durationScale).inWholeMilliseconds.toInt())
- distance = UserActionDistance { _, shadeContentKey, _ ->
- calculateShadePanelTargetPositionY(shadeContentKey)
- }
translate(OverlayShade.Elements.Panel, Edge.Top)
-
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 12b20a53df81..ab31286b78b4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -299,8 +299,7 @@ open class ClockRegistry(
Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE,
)
}
-
- ClockSettings.fromJson(JSONObject(json))
+ json?.let { ClockSettings.fromJson(JSONObject(it)) }
} catch (ex: Exception) {
logger.e("Failed to parse clock settings", ex)
null
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index e2bbe0fef3c0..d7d8d28a71e0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -37,6 +37,7 @@ import com.android.systemui.plugins.clocks.WeatherData
import com.android.systemui.plugins.clocks.ZenData
import com.android.systemui.shared.clocks.view.FlexClockView
import com.android.systemui.shared.clocks.view.HorizontalAlignment
+import com.android.systemui.shared.clocks.view.VerticalAlignment
import java.util.Locale
import java.util.TimeZone
import kotlin.math.max
@@ -255,7 +256,7 @@ class FlexClockFaceController(clockCtx: ClockContext, private val isLargeClock:
timespec = DigitalTimespec.TIME_FULL_FORMAT,
style = FontTextStyle(fontSizeScale = 0.98f),
aodStyle = FontTextStyle(),
- alignment = DigitalAlignment(HorizontalAlignment.LEFT, null),
+ alignment = DigitalAlignment(HorizontalAlignment.LEFT, VerticalAlignment.CENTER),
dateTimeFormat = "h:mm",
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index b8d4bb4b8e77..50762edc1179 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.biometrics
import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.hardware.biometrics.BiometricAuthenticator
@@ -43,6 +42,8 @@ import androidx.test.filters.SmallTest
import com.android.app.viewcapture.ViewCapture
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.launcher3.icons.IconProvider
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -432,8 +433,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
.setMoreOptionsButtonListener(fakeExecutor) { _, _ -> isButtonClicked = true }
.build()
- val container =
- initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView)
+ val container = initializeFingerprintContainer()
waitForIdleSync()
@@ -488,8 +488,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
.build()
val container =
initializeFingerprintContainer(
- authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL,
- contentViewWithMoreOptionsButton = contentView,
+ authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
)
waitForIdleSync()
@@ -500,8 +499,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testCredentialViewUsesEffectiveUserId() {
whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200)
- whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200)))
- .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING)
+ whenever(lockPatternUtils.getCredentialTypeForUser(eq(200)))
+ .thenReturn(CREDENTIAL_TYPE_PATTERN)
val container =
initializeFingerprintContainer(
@@ -578,8 +577,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
addToView: Boolean = true
): TestAuthContainerView {
whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20)
- whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20)))
- .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC)
+ whenever(lockPatternUtils.getCredentialTypeForUser(eq(20))).thenReturn(CREDENTIAL_TYPE_PIN)
// In the credential view, clicking on the background (to cancel authentication) is not
// valid. Thus, the listener should be null, and it should not be in the accessibility
@@ -599,7 +597,6 @@ open class AuthContainerViewTest : SysuiTestCase() {
authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK,
addToView: Boolean = true,
verticalListContentView: PromptVerticalListContentView? = null,
- contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null,
) =
initializeContainer(
TestAuthContainerView(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
index 58fe2c9cbe57..fde847897f72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractorImplTest.kt
@@ -135,9 +135,9 @@ class CredentialInteractorImplTest : SysuiTestCase() {
private fun pinCredential(result: VerifyCredentialResponse, credentialOwner: Int = USER_ID) =
runTest {
val usedAttempts = 1
- whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(USER_ID)))
+ whenever(lockPatternUtils.getCurrentFailedPasswordAttempts(eq(credentialOwner)))
.thenReturn(usedAttempts)
- whenever(lockPatternUtils.verifyCredential(any(), eq(USER_ID), anyInt()))
+ whenever(lockPatternUtils.verifyCredential(any(), eq(credentialOwner), anyInt()))
.thenReturn(result)
whenever(lockPatternUtils.verifyTiedProfileChallenge(any(), eq(USER_ID), anyInt()))
.thenReturn(result)
@@ -170,7 +170,7 @@ class CredentialInteractorImplTest : SysuiTestCase() {
assertThat(successfulResult).isNotNull()
assertThat(successfulResult!!.hat).isEqualTo(result.gatekeeperHAT)
- verify(lockPatternUtils).userPresent(eq(USER_ID))
+ verify(lockPatternUtils).userPresent(eq(credentialOwner))
verify(lockPatternUtils)
.removeGatekeeperPasswordHandle(eq(result.gatekeeperPasswordHandle))
} else {
@@ -190,13 +190,13 @@ class CredentialInteractorImplTest : SysuiTestCase() {
.hasSize(statusList.size)
verify(lockPatternUtils)
- .setLockoutAttemptDeadline(eq(USER_ID), eq(result.timeout))
+ .setLockoutAttemptDeadline(eq(credentialOwner), eq(result.timeout))
} else { // failed
assertThat(failedResult.error)
.matches(Regex("(.*)try again(.*)", RegexOption.IGNORE_CASE).toPattern())
assertThat(statusList).isEmpty()
- verify(lockPatternUtils).reportFailedPasswordAttempt(eq(USER_ID))
+ verify(lockPatternUtils).reportFailedPasswordAttempt(eq(credentialOwner))
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index eb1f1d9c52f4..5c983656225e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -51,6 +51,7 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,6 +79,32 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE)
setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE)
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ false,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
+ )
}
@EnableFlags(FLAG_COMMUNAL_HUB)
@@ -334,6 +361,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_charging_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING)
+ }
+
+ @Test
fun whenToDream_docked() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
@@ -348,6 +387,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_docked_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED)
+ }
+
+ @Test
fun whenToDream_postured() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
@@ -362,6 +413,18 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ fun whenToDream_postured_defaultValue() =
+ kosmos.runTest {
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ true,
+ )
+
+ val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
+ assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED)
+ }
+
+ @Test
fun whenToDream_default() =
kosmos.runTest {
val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index c9e7a5d7df05..c3cc3e66f81f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -21,6 +21,7 @@ import android.app.admin.DevicePolicyManager
import android.app.admin.devicePolicyManager
import android.content.Intent
import android.content.pm.UserInfo
+import android.content.res.mainResources
import android.os.UserHandle
import android.os.UserManager
import android.os.userManager
@@ -85,6 +86,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.advanceTimeBy
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -106,7 +108,10 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN)
private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0)
- private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val kosmos =
+ testKosmos()
+ .apply { mainResources = mContext.orCreateTestableResources.resources }
+ .useUnconfinedTestDispatcher()
private val Kosmos.underTest by Kosmos.Fixture { communalInteractor }
@@ -122,6 +127,32 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB)
+
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault,
+ false,
+ )
+ mContext.orCreateTestableResources.addOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault,
+ false,
+ )
+ }
+
+ @After
+ fun tearDown() {
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault
+ )
+ mContext.orCreateTestableResources.removeOverride(
+ com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault
+ )
}
@Test
@@ -398,6 +429,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun ctaTile_showsByDefault() =
kosmos.runTest {
fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
@@ -412,6 +444,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun ctaTile_afterDismiss_doesNotShow() =
kosmos.runTest {
// Set to main user, so we can dismiss the tile for the main user.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index b70f46c4b01c..7866a7f01658 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.Flags.FLAG_BOUNCER_UI_REVAMP
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_RESPONSIVE_GRID
import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_DIRECT_EDIT_MODE
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
import com.android.systemui.communal.data.model.CommunalSmartspaceTimer
@@ -219,6 +220,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun ordering_smartspaceBeforeUmoBeforeWidgetsBeforeCtaTile() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
@@ -258,7 +260,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
/** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
- @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID, FLAG_GLANCEABLE_HUB_V2)
fun ongoingContent_umoAndOneTimer_sizedAppropriately() =
testScope.runTest {
// Widgets available.
@@ -296,7 +298,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
/** TODO(b/378171351): Handle ongoing content in responsive grid. */
@Test
- @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID)
+ @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID, FLAG_GLANCEABLE_HUB_V2)
fun ongoingContent_umoAndTwoTimers_sizedAppropriately() =
testScope.runTest {
// Widgets available.
@@ -342,6 +344,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun communalContent_mediaHostVisible_umoIncluded() =
testScope.runTest {
// Media playing.
@@ -353,6 +356,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun communalContent_mediaHostVisible_umoExcluded() =
testScope.runTest {
whenever(mediaHost.visible).thenReturn(false)
@@ -408,6 +412,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
setIsMainUser(true)
@@ -734,6 +739,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun communalContent_emitsFrozenContent_whenFrozen() =
testScope.runTest {
val communalContent by collectLastValue(underTest.communalContent)
@@ -790,6 +796,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
}
@Test
+ @DisableFlags(FLAG_GLANCEABLE_HUB_V2)
fun communalContent_emitsLatestContent_whenNotFrozen() =
testScope.runTest {
val communalContent by collectLastValue(underTest.communalContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 030233625027..39baa01e07d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -570,7 +570,7 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
unlockDevice()
assertThat(isUnlocked).isTrue()
- underTest.lockNow()
+ underTest.lockNow("test")
runCurrent()
assertThat(isUnlocked).isFalse()
@@ -597,6 +597,62 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() {
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
}
+ @Test
+ fun deviceUnlockStatus_staysUnlocked_whenDeviceGoesToSleep_whileIsTrusted() =
+ testScope.runTest {
+ setLockAfterScreenTimeout(5000)
+ kosmos.fakeAuthenticationRepository.powerButtonInstantlyLocks = false
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+
+ kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_staysUnlocked_whileIsTrusted() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+ unlockDevice()
+
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON
+ )
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ }
+
+ @Test
+ fun deviceUnlockStatus_becomesLocked_whenNoLongerTrusted_whileAsleep() =
+ testScope.runTest {
+ val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+ unlockDevice()
+ kosmos.powerInteractor.setAsleepForTest(
+ sleepReason = PowerManager.GO_TO_SLEEP_REASON_SLEEP_BUTTON
+ )
+ runCurrent()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(false)
+ runCurrent()
+
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ }
+
private fun TestScope.unlockDevice() {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
index 597629219634..09c4231b7178 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -275,7 +275,9 @@ class DumpHandlerTest : SysuiTestCase() {
@Test
fun testDumpAllProtoDumpables() {
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
val args = arrayOf(DumpHandler.PROTO)
@@ -287,7 +289,9 @@ class DumpHandlerTest : SysuiTestCase() {
@Test
fun testDumpSingleProtoDumpable() {
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
+ @Suppress("DEPRECATION")
dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
val args = arrayOf(DumpHandler.PROTO, "protoDumpable1")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
index 6899d23bcfd7..e126b4d7265c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorParameterizedTest.kt
@@ -57,7 +57,6 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorParameterizedTest(private val gestureType: GestureType) :
SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index dc393c01dce1..adcd849db49a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -50,7 +50,6 @@ import org.mockito.kotlin.verify
@SmallTest
@RunWith(AndroidJUnit4::class)
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyboardTouchpadEduInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
index ebc00c3897cb..9283455d3e54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/FakeNoteTaskBubbleController.kt
@@ -38,7 +38,7 @@ class FakeNoteTaskBubbleController(
) : NoteTaskBubblesController(unUsed1, unsUsed2) {
override suspend fun areBubblesAvailable() = optionalBubbles.isPresent
- override suspend fun showOrHideAppBubble(
+ override suspend fun showOrHideNoteBubble(
intent: Intent,
userHandle: UserHandle,
icon: Icon,
@@ -49,12 +49,12 @@ class FakeNoteTaskBubbleController(
if (
bubbleExpandBehavior == NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED &&
bubbles.isBubbleExpanded(
- Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle)
+ Bubble.getNoteBubbleKeyForApp(intent.`package`, userHandle)
)
) {
return@ifPresentOrElse
}
- bubbles.showOrHideAppBubble(intent, userHandle, icon)
+ bubbles.showOrHideNoteBubble(intent, userHandle, icon)
},
{ throw IllegalAccessException() },
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
index e55d6ad6c5a0..7b9af90ff228 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt
@@ -64,41 +64,41 @@ internal class NoteTaskBubblesServiceTest : SysuiTestCase() {
}
@Test
- fun showOrHideAppBubble_defaultExpandBehavior_shouldCallBubblesApi() {
+ fun showOrHideNoteBubble_defaultExpandBehavior_shouldCallBubblesApi() {
val intent = Intent()
val user = UserHandle.SYSTEM
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.DEFAULT
whenever(bubbles.isBubbleExpanded(any())).thenReturn(false)
- createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
+ createServiceBinder().showOrHideNoteBubble(intent, user, icon, bubbleExpandBehavior)
- verify(bubbles).showOrHideAppBubble(intent, user, icon)
+ verify(bubbles).showOrHideNoteBubble(intent, user, icon)
}
@Test
- fun showOrHideAppBubble_keepIfExpanded_bubbleShown_shouldNotCallBubblesApi() {
+ fun showOrHideNoteBubble_keepIfExpanded_bubbleShown_shouldNotCallBubblesApi() {
val intent = Intent().apply { setPackage("test") }
val user = UserHandle.SYSTEM
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED
whenever(bubbles.isBubbleExpanded(any())).thenReturn(true)
- createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
+ createServiceBinder().showOrHideNoteBubble(intent, user, icon, bubbleExpandBehavior)
- verify(bubbles, never()).showOrHideAppBubble(intent, user, icon)
+ verify(bubbles, never()).showOrHideNoteBubble(intent, user, icon)
}
@Test
- fun showOrHideAppBubble_keepIfExpanded_bubbleNotShown_shouldCallBubblesApi() {
+ fun showOrHideNoteBubble_keepIfExpanded_bubbleNotShown_shouldCallBubblesApi() {
val intent = Intent().apply { setPackage("test") }
val user = UserHandle.SYSTEM
val icon = Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
val bubbleExpandBehavior = NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED
whenever(bubbles.isBubbleExpanded(any())).thenReturn(false)
- createServiceBinder().showOrHideAppBubble(intent, user, icon, bubbleExpandBehavior)
+ createServiceBinder().showOrHideNoteBubble(intent, user, icon, bubbleExpandBehavior)
- verify(bubbles).showOrHideAppBubble(intent, user, icon)
+ verify(bubbles).showOrHideNoteBubble(intent, user, icon)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
index 98770c724126..c5d679f5df05 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/DetailsViewModelTest.kt
@@ -62,8 +62,8 @@ class DetailsViewModelTest : SysuiTestCase() {
val tiles = currentTilesInteractor.currentTiles.value
assertThat(currentTilesInteractor.currentTilesSpecs.size).isEqualTo(2)
- assertThat(tiles!![1].spec).isEqualTo(specNoDetails)
- (tiles!![1].tile as FakeQSTile).hasDetailsViewModel = false
+ assertThat(tiles[1].spec).isEqualTo(specNoDetails)
+ (tiles[1].tile as FakeQSTile).hasDetailsViewModel = false
assertThat(underTest.activeTileDetails).isNull()
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 af30e435da73..5648c62fb439 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
@@ -2697,6 +2697,34 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(isVisible).isFalse()
}
+ @Test
+ fun deviceLocks_whenNoLongerTrusted_whileDeviceNotEntered() =
+ testScope.runTest {
+ prepareState(isDeviceUnlocked = true, initialSceneKey = Scenes.Gone)
+ underTest.start()
+
+ val isDeviceEntered by collectLastValue(kosmos.deviceEntryInteractor.isDeviceEntered)
+ val deviceUnlockStatus by
+ collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ assertThat(isDeviceEntered).isTrue()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(Scenes.Gone)
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(true)
+ kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "reason")
+ runCurrent()
+ assertThat(isDeviceEntered).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isTrue()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+
+ kosmos.fakeTrustRepository.setCurrentUserTrusted(false)
+ runCurrent()
+
+ assertThat(isDeviceEntered).isFalse()
+ assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ }
+
private fun TestScope.emulateSceneTransition(
transitionStateFlow: MutableStateFlow<ObservableTransitionState>,
toScene: SceneKey,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
index d8897e9048c3..81301b3886f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java
@@ -183,14 +183,14 @@ public final class AppClipsServiceTest extends SysuiTestCase {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
+ when(mBubbles.isNoteBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(false);
}
private void mockForScreenshotBlocked() {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mBubbles.isNoteBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(true);
}
@@ -199,7 +199,7 @@ public final class AppClipsServiceTest extends SysuiTestCase {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mBubbles.isNoteBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
}
@@ -208,7 +208,7 @@ public final class AppClipsServiceTest extends SysuiTestCase {
when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true);
when(mOptionalBubbles.isEmpty()).thenReturn(false);
when(mOptionalBubbles.get()).thenReturn(mBubbles);
- when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
+ when(mBubbles.isNoteBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true);
when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false);
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
index 75262a4d058d..03c07510ce53 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt
@@ -24,9 +24,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.activityStarter
import com.android.systemui.res.R
@@ -47,6 +47,7 @@ import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernizat
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel
import com.android.systemui.statusbar.phone.ongoingcall.shared.model.inCallModel
+import com.android.systemui.testKosmos
import com.android.systemui.util.time.fakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
@@ -60,7 +61,7 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class CallChipViewModelTest : SysuiTestCase() {
- private val kosmos = Kosmos()
+ private val kosmos = testKosmos()
private val notificationListRepository = kosmos.activeNotificationListRepository
private val testScope = kosmos.testScope
private val repo = kosmos.ongoingCallRepository
@@ -162,25 +163,34 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon() =
+ fun chip_zeroStartTime_cdFlagOff_iconIsNotifIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
val notifIcon = createStatusBarIconViewOrNull()
- repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
+ repo.setOngoingCallState(
+ inCallModel(
+ startTimeMs = 0,
+ notificationIcon = notifIcon,
+ appName = "Fake app name",
+ )
+ )
assertThat((latest as OngoingActivityChipModel.Shown).icon)
.isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
val actualIcon =
- (((latest as OngoingActivityChipModel.Shown).icon)
- as OngoingActivityChipModel.ChipIcon.StatusBarView)
- .impl
- assertThat(actualIcon).isEqualTo(notifIcon)
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarView
+ assertThat(actualIcon.impl).isEqualTo(notifIcon)
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon() =
+ fun chip_zeroStartTime_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -189,11 +199,22 @@ class CallChipViewModelTest : SysuiTestCase() {
startTimeMs = 0,
notificationIcon = createStatusBarIconViewOrNull(),
notificationKey = "notifKey",
+ appName = "Fake app name",
)
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+ )
+ val actualIcon =
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
@@ -216,7 +237,7 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
- fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() =
+ fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOn_iconIsNotifKeyIcon_withContentDescription() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -225,11 +246,22 @@ class CallChipViewModelTest : SysuiTestCase() {
startTimeMs = 1000,
notificationIcon = null,
notificationKey = "notifKey",
+ appName = "Fake app name",
)
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
+ )
+ val actualIcon =
+ (latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ assertThat(actualIcon.notificationKey).isEqualTo("notifKey")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Ongoing call")
+ assertThat(actualIcon.contentDescription.loadContentDescription(context))
+ .contains("Fake app name")
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
index fe15eac46e2d..05f2585cfaa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt
@@ -49,6 +49,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val startingNotif =
activeNotificationModel(
key = "notif1",
+ appName = "Fake Name",
statusBarChipIcon = icon,
promotedContent = PROMOTED_CONTENT,
)
@@ -58,6 +59,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.notificationChip)
assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.appName).isEqualTo("Fake Name")
assertThat(latest!!.statusBarChipIconView).isEqualTo(icon)
assertThat(latest!!.promotedContent).isEqualTo(PROMOTED_CONTENT)
}
@@ -70,6 +72,7 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
factory.create(
activeNotificationModel(
key = "notif1",
+ appName = "Fake Name",
statusBarChipIcon = originalIconView,
promotedContent = PROMOTED_CONTENT,
),
@@ -82,12 +85,14 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
underTest.setNotification(
activeNotificationModel(
key = "notif1",
+ appName = "New Name",
statusBarChipIcon = newIconView,
promotedContent = PROMOTED_CONTENT,
)
)
assertThat(latest!!.key).isEqualTo("notif1")
+ assertThat(latest!!.appName).isEqualTo("New Name")
assertThat(latest!!.statusBarChipIconView).isEqualTo(newIconView)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
index 942e6554e5d9..1f77ddc73aa5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.Context
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.View
@@ -23,6 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.collectLastValue
@@ -125,7 +127,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_onePromotedNotif_statusBarIconViewMatches() =
+ fun chips_onePromotedNotif_connectedDisplaysFlagDisabled_statusBarIconViewMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -134,6 +136,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
+ appName = "Fake App Name",
statusBarChipIcon = icon,
promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
@@ -142,7 +145,13 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertIsNotifChip(chip, icon, "notif")
+ assertIsNotifChip(
+ chip,
+ context,
+ icon,
+ expectedNotificationKey = "notif",
+ expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+ )
}
@Test
@@ -157,6 +166,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = notifKey,
+ appName = "Fake App Name",
statusBarChipIcon = null,
promotedContent = PromotedNotificationContentModel.Builder(notifKey).build(),
)
@@ -165,9 +175,13 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat(chip.icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notifKey))
+ assertIsNotifChip(
+ chip,
+ context,
+ expectedIcon = null,
+ expectedNotificationKey = "notif",
+ expectedContentDescriptionSubstrings = listOf("Ongoing", "Fake App Name"),
+ )
}
@Test
@@ -230,8 +244,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
)
assertThat(latest).hasSize(2)
- assertIsNotifChip(latest!![0], firstIcon, "notif1")
- assertIsNotifChip(latest!![1], secondIcon, "notif2")
+ assertIsNotifChip(latest!![0], context, firstIcon, "notif1")
+ assertIsNotifChip(latest!![1], context, secondIcon, "notif2")
}
@Test
@@ -590,7 +604,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
// THEN the "notif" chip keeps showing time
val chip = latest!![0]
assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java)
- assertIsNotifChip(chip, icon, "notif")
+ assertIsNotifChip(chip, context, icon, "notif")
}
@Test
@@ -705,24 +719,41 @@ class NotifChipsViewModelTest : SysuiTestCase() {
companion object {
fun assertIsNotifChip(
latest: OngoingActivityChipModel?,
+ context: Context,
expectedIcon: StatusBarIconView?,
- notificationKey: String,
+ expectedNotificationKey: String,
+ expectedContentDescriptionSubstrings: List<String> = emptyList(),
) {
val shown = latest as OngoingActivityChipModel.Shown
if (StatusBarConnectedDisplays.isEnabled) {
assertThat(shown.icon)
- .isEqualTo(
- OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notificationKey)
+ .isInstanceOf(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon::class.java
)
+ val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+
+ assertThat(icon.notificationKey).isEqualTo(expectedNotificationKey)
+ expectedContentDescriptionSubstrings.forEach {
+ assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+ }
} else {
- assertThat(latest.icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon!!))
+ assertThat(shown.icon)
+ .isInstanceOf(OngoingActivityChipModel.ChipIcon.StatusBarView::class.java)
+ val icon = shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarView
+ assertThat(icon.impl).isEqualTo(expectedIcon!!)
+ expectedContentDescriptionSubstrings.forEach {
+ assertThat(icon.contentDescription.loadContentDescription(context)).contains(it)
+ }
}
}
fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(expectedKey))
+ assertThat(
+ ((latest as OngoingActivityChipModel.Shown).icon
+ as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon)
+ .notificationKey
+ )
+ .isEqualTo(expectedKey)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
index 28f360108e50..78103a9906c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -311,7 +311,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, icon, "notif")
+ assertIsNotifChip(latest!!.primary, context, icon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@@ -339,8 +339,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
- assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
}
@Test
@@ -374,8 +374,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
- assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
+ assertIsNotifChip(latest!!.primary, context, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, secondIcon, "secondNotif")
}
@Test
@@ -407,7 +407,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
assertIsCallChip(latest!!.primary, callNotificationKey)
- assertIsNotifChip(latest!!.secondary, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, context, firstIcon, "firstNotif")
}
@Test
@@ -456,7 +456,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.primaryChip)
- assertIsNotifChip(latest, notifIcon, "notif")
+ assertIsNotifChip(latest, context, notifIcon, "notif")
// WHEN the higher priority call chip is added
callRepo.setOngoingCallState(
@@ -527,7 +527,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
callRepo.setOngoingCallState(OngoingCallModel.NoCall)
// THEN the lower priority notif is used
- assertIsNotifChip(latest, notifIcon, "notif")
+ assertIsNotifChip(latest, context, notifIcon, "notif")
}
@Test
@@ -552,7 +552,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chips)
- assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
// WHEN the higher priority call chip is added
@@ -563,7 +563,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// THEN the higher priority call chip is used as primary and notif is demoted to
// secondary
assertIsCallChip(latest!!.primary, callNotificationKey)
- assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -590,13 +590,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// THEN media projection and notif remain
assertIsShareToAppChip(latest!!.primary)
- assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.secondary, context, notifIcon, "notif")
// WHEN media projection is dropped
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN notif is promoted to primary
- assertIsNotifChip(latest!!.primary, notifIcon, "notif")
+ assertIsNotifChip(latest!!.primary, context, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
index c06da4bc5080..dc65a9e24407 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.core
import android.platform.test.annotations.EnableFlags
import android.view.Display
+import android.view.mockIWindowManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -31,11 +32,13 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -51,75 +54,110 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore
private val fakePrivacyDotStore = kosmos.fakePrivacyDotWindowControllerStore
private val fakeLightBarStore = kosmos.fakeLightBarControllerStore
+ private val windowManager = kosmos.mockIWindowManager
+
// Lazy, so that @EnableFlags is set before initializer is instantiated.
private val underTest by lazy { kosmos.multiDisplayStatusBarStarter }
+ @Before
+ fun setup() {
+ whenever(windowManager.shouldShowSystemDecors(Display.DEFAULT_DISPLAY)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_1)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_2)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_3)).thenReturn(true)
+ whenever(windowManager.shouldShowSystemDecors(DISPLAY_4_NO_SYSTEM_DECOR)).thenReturn(false)
+ }
+
@Test
fun start_startsInitializersForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_1).startedByCoreStartable)
.isTrue()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_2).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
fun start_startsOrchestratorForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_1)!!)
+ .start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_2)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
fun start_startsPrivacyDotForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 1)).start()
- verify(fakePrivacyDotStore.forDisplay(displayId = 2)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_1)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_2)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
fun start_doesNotStartLightBarControllerForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 1), never()).start()
- verify(fakeLightBarStore.forDisplay(displayId = 2), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_1), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_2), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
fun start_createsLightBarControllerForCurrentDisplays() =
testScope.runTest {
- fakeDisplayRepository.addDisplay(displayId = 1)
- fakeDisplayRepository.addDisplay(displayId = 2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_1)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_2)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
underTest.start()
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, 2)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(1, DISPLAY_2)
}
@Test
@@ -135,121 +173,174 @@ class MultiDisplayStatusBarStarterTest : SysuiTestCase() {
}
@Test
- fun displayAdded_orchestratorForNewDisplayIsStarted() =
+ fun displayAdded_orchestratorForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
- fun displayAdded_initializerForNewDisplayIsStarted() =
+ fun displayAdded_initializerForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
- fun displayAdded_privacyDotForNewDisplayIsStarted() =
+ fun displayAdded_privacyDotForNewDisplay() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAdded_lightBarForNewDisplayIsCreated() =
+ fun displayAdded_lightBarForNewDisplayCreate() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
}
@Test
- fun displayAdded_lightBarForNewDisplayIsNotStarted() =
+ fun displayAdded_lightBarForNewDisplayStart() =
testScope.runTest {
underTest.start()
runCurrent()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAddedDuringStart_initializerForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_initializerForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
expect
- .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable)
+ .that(fakeInitializerStore.forDisplay(displayId = DISPLAY_3).startedByCoreStartable)
.isTrue()
+ expect
+ .that(
+ fakeInitializerStore
+ .forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
+ .startedByCoreStartable
+ )
+ .isFalse()
}
@Test
- fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_orchestratorForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start()
+ verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = DISPLAY_3)!!)
+ .start()
+ assertThat(
+ fakeOrchestratorFactory.createdOrchestratorForDisplay(
+ displayId = DISPLAY_4_NO_SYSTEM_DECOR
+ )
+ )
+ .isNull()
}
@Test
- fun displayAddedDuringStart_privacyDotForNewDisplayIsStarted() =
+ fun displayAddedDuringStart_privacyDotForNewDisplay() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakePrivacyDotStore.forDisplay(displayId = 3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_3)).start()
+ verify(fakePrivacyDotStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayIsCreated() =
+ fun displayAddedDuringStart_lightBarForNewDisplayCreate() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(3)
+ assertThat(fakeLightBarStore.perDisplayMocks.keys).containsExactly(DISPLAY_3)
}
@Test
- fun displayAddedDuringStart_lightBarForNewDisplayIsNotStarted() =
+ fun displayAddedDuringStart_lightBarForNewDisplayStart() =
testScope.runTest {
underTest.start()
- fakeDisplayRepository.addDisplay(displayId = 3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_3)
+ fakeDisplayRepository.addDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR)
runCurrent()
- verify(fakeLightBarStore.forDisplay(displayId = 3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_3), never()).start()
+ verify(fakeLightBarStore.forDisplay(displayId = DISPLAY_4_NO_SYSTEM_DECOR), never())
+ .start()
}
+
+ companion object {
+ const val DISPLAY_1 = 1
+ const val DISPLAY_2 = 2
+ const val DISPLAY_3 = 3
+ const val DISPLAY_4_NO_SYSTEM_DECOR = 4
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 5d9aa71c5d89..35b19c19d5ce 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -47,7 +47,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
private val notifsRepository = kosmos.activeNotificationListRepository
private val notifsInteractor = kosmos.activeNotificationsInteractor
private val underTest =
- RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock())
+ RenderNotificationListInteractor(notifsRepository, sectionStyleProvider = mock(), context)
@Test
fun setRenderedList_preservesOrdering() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
index 115edd0d3bb0..2b16c00107c3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wallpapers/data/repository/WallpaperRepositoryImplTest.kt
@@ -25,16 +25,15 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardClockRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.res.R as SysUIR
import com.android.systemui.shared.Flags as SharedFlags
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.wallpapers.data.repository.WallpaperRepositoryImpl.Companion.MAGIC_PORTRAIT_CLASSNAME
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -72,9 +71,12 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
)
}
+ lateinit var focalAreaTarget: String
+
@Before
fun setUp() {
whenever(wallpaperManager.isWallpaperSupported).thenReturn(true)
+ focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
}
@Test
@@ -248,17 +250,17 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
- fun shouldSendNotificationLayout_setMagicPortraitWallpaper_launchSendLayoutJob() =
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setExtendedEffectsWallpaper_launchSendLayoutJob() =
testScope.runTest {
val latest by collectLastValue(underTest.shouldSendFocalArea)
- val magicPortraitWallpaper =
+ val extedendEffectsWallpaper =
mock<WallpaperInfo>().apply {
- whenever(this.component)
- .thenReturn(ComponentName(context, MAGIC_PORTRAIT_CLASSNAME))
+ whenever(this.component).thenReturn(ComponentName(context, focalAreaTarget))
}
+
whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(magicPortraitWallpaper)
+ .thenReturn(extedendEffectsWallpaper)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -269,13 +271,16 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_MAGIC_PORTRAIT_WALLPAPERS)
- fun shouldSendNotificationLayout_setNotMagicPortraitWallpaper_cancelSendLayoutJob() =
+ @EnableFlags(SharedFlags.FLAG_EXTENDED_WALLPAPER_EFFECTS)
+ fun shouldSendNotificationLayout_setNotExtendedEffectsWallpaper_cancelSendLayoutJob() =
testScope.runTest {
val latest by collectLastValue(underTest.shouldSendFocalArea)
- val magicPortraitWallpaper = MAGIC_PORTRAIT_WP
+ val extendedEffectsWallpaper =
+ mock<WallpaperInfo>().apply {
+ whenever(this.component).thenReturn(ComponentName("", focalAreaTarget))
+ }
whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(magicPortraitWallpaper)
+ .thenReturn(extendedEffectsWallpaper)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -284,9 +289,7 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
assertThat(underTest.sendLockscreenLayoutJob).isNotNull()
assertThat(underTest.sendLockscreenLayoutJob!!.isActive).isEqualTo(true)
- val nonMagicPortraitWallpaper = UNSUPPORTED_WP
- whenever(wallpaperManager.getWallpaperInfoForUser(any()))
- .thenReturn(nonMagicPortraitWallpaper)
+ whenever(wallpaperManager.getWallpaperInfoForUser(any())).thenReturn(UNSUPPORTED_WP)
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
Intent(Intent.ACTION_WALLPAPER_CHANGED),
@@ -303,10 +306,5 @@ class WallpaperRepositoryImplTest : SysuiTestCase() {
val USER_WITH_SUPPORTED_WP = UserInfo(/* id= */ 4, /* name= */ "user4", /* flags= */ 0)
val SUPPORTED_WP =
mock<WallpaperInfo>().apply { whenever(this.supportsAmbientMode()).thenReturn(true) }
-
- val MAGIC_PORTRAIT_WP =
- mock<WallpaperInfo>().apply {
- whenever(this.component).thenReturn(ComponentName("", MAGIC_PORTRAIT_CLASSNAME))
- }
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 75c174229564..ab6fbc3f47a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -30,7 +30,7 @@ import com.android.wm.shell.bubbles.BubbleData;
import com.android.wm.shell.bubbles.BubbleDataRepository;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubblePositioner;
-import com.android.wm.shell.bubbles.properties.BubbleProperties;
+import com.android.wm.shell.bubbles.ResizabilityChecker;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -82,14 +82,14 @@ public class TestableBubbleController extends BubbleController {
Transitions transitions,
SyncTransactionQueue syncQueue,
IWindowManager wmService,
- BubbleProperties bubbleProperties) {
+ ResizabilityChecker resizabilityChecker) {
super(context, shellInit, shellCommandHandler, shellController, data, Runnable::run,
floatingContentCoordinator, dataRepository, statusBarService, windowManager,
displayInsetsController, displayImeController, userManager, launcherApps,
bubbleLogger, taskStackListener, shellTaskOrganizer, positioner, displayController,
oneHandedOptional, dragAndDropController, shellMainExecutor, shellMainHandler,
new SyncExecutor(), taskViewRepository, taskViewTransitions, transitions,
- syncQueue, wmService, bubbleProperties);
+ syncQueue, wmService, resizabilityChecker);
setInflateSynchronously(true);
onInit();
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
index eab7d7913129..be0362fd7481 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/TileDetailsViewModel.kt
@@ -16,17 +16,10 @@
package com.android.systemui.plugins.qs
-import androidx.compose.runtime.Composable
-
/**
* The base view model class for rendering the Tile's TileDetailsView.
*/
abstract class TileDetailsViewModel {
-
- // The view content of this tile details view.
- @Composable
- abstract fun GetContentView()
-
// The callback when the settings button is clicked. Currently this is the same as the on tile
// long press callback
abstract fun clickOnSettingsButton()
diff --git a/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
index 800060db7757..6533460a9c99 100644
--- a/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
+++ b/packages/SystemUI/res/drawable/notif_footer_btn_settings.xml
@@ -6,5 +6,5 @@
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
- android:pathData="M370,880L354,752Q341,747 329.5,740Q318,733 307,725L188,775L78,585L181,507Q180,500 180,493.5Q180,487 180,480Q180,473 180,466.5Q180,460 181,453L78,375L188,185L307,235Q318,227 330,220Q342,213 354,208L370,80L590,80L606,208Q619,213 630.5,220Q642,227 653,235L772,185L882,375L779,453Q780,460 780,466.5Q780,473 780,480Q780,487 780,493.5Q780,500 778,507L881,585L771,775L653,725Q642,733 630,740Q618,747 606,752L590,880L370,880ZM440,800L519,800L533,694Q564,686 590.5,670.5Q617,655 639,633L738,674L777,606L691,541Q696,527 698,511.5Q700,496 700,480Q700,464 698,448.5Q696,433 691,419L777,354L738,286L639,328Q617,305 590.5,289.5Q564,274 533,266L520,160L441,160L427,266Q396,274 369.5,289.5Q343,305 321,327L222,286L183,354L269,418Q264,433 262,448Q260,463 260,480Q260,496 262,511Q264,526 269,541L183,606L222,674L321,632Q343,655 369.5,670.5Q396,686 427,694L440,800ZM482,620Q540,620 581,579Q622,538 622,480Q622,422 581,381Q540,340 482,340Q423,340 382.5,381Q342,422 342,480Q342,538 382.5,579Q423,620 482,620ZM480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Z"/>
+ android:pathData="M480,864q-30,0 -51,-21t-21,-51h144q0,30 -21,51t-51,21ZM192,744v-72h48v-240q0,-88 55.5,-155T439,196q-5,17 -7,34t-2,34q0,59 23.5,109.5T517,461q40,37 92,56t111,14v141h48v72L192,744ZM664,456 L652,400q-14,-5 -26.5,-11.5T602,372l-55,17 -32,-55 41,-40q-3,-14 -3,-29t3,-29l-41,-39 32,-56 54,16q11,-11 24,-18t27,-11l13,-56h64l13,56q14,5 27.5,11.5T793,157l54,-15 32,55 -40,38q3,14 2.5,29.5T838,294l41,39 -32,55 -55,-16q-11,10 -23.5,16.5T742,400l-14,56h-64ZM697,336q30,0 51,-21t21,-51q0,-30 -21,-51t-51,-21q-30,0 -51,21t-21,51q0,30 21,51t51,21Z"/>
</vector>
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid.xml b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
index 8c34cd4165e0..8fd10fb3ddb8 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid.xml
@@ -29,6 +29,7 @@
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
android:paddingEnd="4dp"
/>
<TextView
diff --git a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
index a338e4c70cfa..35f2ef901bdd 100644
--- a/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
+++ b/packages/SystemUI/res/layout/notification_2025_hybrid_conversation.xml
@@ -54,6 +54,7 @@
android:singleLine="true"
android:paddingEnd="4dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textSize="@*android:dimen/notification_2025_title_text_size"
/>
<TextView
diff --git a/packages/SystemUI/res/values-xlarge-land/config.xml b/packages/SystemUI/res/values-xlarge-land/config.xml
index 6d8b64ade259..4c77f30f2e69 100644
--- a/packages/SystemUI/res/values-xlarge-land/config.xml
+++ b/packages/SystemUI/res/values-xlarge-land/config.xml
@@ -16,5 +16,5 @@
<resources>
<item name="shortcut_helper_screen_width_fraction" format="float" type="dimen">0.8</item>
- <bool name="center_align_magic_portrait_shape">true</bool>
+ <bool name="center_align_focal_area_shape">true</bool>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 68e33f27aefa..9b8926e921c9 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1107,7 +1107,7 @@
<!-- The dream component used when the device is low light environment. -->
<string translatable="false" name="config_lowLightDreamComponent"/>
- <!--Whether we should position magic portrait shape effects in the center of lockscreen
- it's false by default, and only be true in tablet landscape -->
- <bool name="center_align_magic_portrait_shape">false</bool>
+ <!-- Configuration for wallpaper focal area -->
+ <bool name="center_align_focal_area_shape">false</bool>
+ <string name="focal_area_target" translatable="false" />
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index fc9635bb6e45..7b2e8126b0c2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3401,6 +3401,8 @@
<!-- Content description for a chip in the status bar showing that the user is currently on a call. [CHAR LIMIT=NONE] -->
<string name="ongoing_call_content_description">Ongoing call</string>
+ <!-- Content description for a chip in the status bar showing that the user currently has an ongoing activity. [CHAR LIMIT=NONE]-->
+ <string name="ongoing_notification_extra_content_description">Ongoing</string>
<!-- Provider Model: Default title of the mobile network in the mobile layout. [CHAR LIMIT=50] -->
<string name="mobile_data_settings_title">Mobile data</string>
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 870e6e6bf803..5b99a3f16fc2 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -17,13 +17,6 @@ package com.android.systemui.biometrics
import android.Manifest
import android.app.ActivityTaskManager
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX
-import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Bitmap
@@ -44,6 +37,9 @@ import android.view.WindowMetrics
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD
+import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN
+import com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN
import com.android.systemui.biometrics.shared.model.PromptKind
object Utils {
@@ -80,7 +76,7 @@ object Utils {
view.notifySubtreeAccessibilityStateChanged(
view,
view,
- AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE,
)
}
@@ -94,14 +90,10 @@ object Utils {
@JvmStatic
fun getCredentialType(utils: LockPatternUtils, userId: Int): PromptKind =
- when (utils.getKeyguardStoredPasswordQuality(userId)) {
- PASSWORD_QUALITY_SOMETHING -> PromptKind.Pattern
- PASSWORD_QUALITY_NUMERIC,
- PASSWORD_QUALITY_NUMERIC_COMPLEX -> PromptKind.Pin
- PASSWORD_QUALITY_ALPHABETIC,
- PASSWORD_QUALITY_ALPHANUMERIC,
- PASSWORD_QUALITY_COMPLEX,
- PASSWORD_QUALITY_MANAGED -> PromptKind.Password
+ when (utils.getCredentialTypeForUser(userId)) {
+ CREDENTIAL_TYPE_PATTERN -> PromptKind.Pattern
+ CREDENTIAL_TYPE_PIN -> PromptKind.Pin
+ CREDENTIAL_TYPE_PASSWORD -> PromptKind.Password
else -> PromptKind.Password
}
@@ -112,7 +104,7 @@ object Utils {
@JvmStatic
fun <T : SensorPropertiesInternal> findFirstSensorProperties(
properties: List<T>?,
- sensorIds: IntArray
+ sensorIds: IntArray,
): T? = properties?.firstOrNull { sensorIds.contains(it.sensorId) }
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
index 3a9a238d90a6..cadef52ce4cc 100644
--- a/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt
@@ -41,7 +41,7 @@ class BootCompleteCacheImpl @Inject constructor(dumpManager: DumpManager) :
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
}
@GuardedBy("listeners")
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 21569c13fab4..64a46c0d7f00 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -131,7 +131,7 @@ abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
override fun onViewAttached() {
dialogManager.registerListener(dialogListener)
- dumpManager.registerDumpable(dumpTag, this)
+ dumpManager.registerNormalDumpable(dumpTag, this)
udfpsOverlayInteractor.setHandleTouches(shouldHandle = !shouldPauseAuth())
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 08b3e99fadd0..84d3d7f81c25 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -72,10 +72,9 @@ constructor(
// Request LockSettingsService to return the Gatekeeper Password in the
// VerifyCredentialResponse so that we can request a Gatekeeper HAT with the
// Gatekeeper Password and operationId.
- var effectiveUserId = request.userInfo.deviceCredentialOwnerId
+ val effectiveUserId = request.userInfo.deviceCredentialOwnerId
val response =
if (Flags.privateSpaceBp() && effectiveUserId != request.userInfo.userId) {
- effectiveUserId = request.userInfo.userId
lockPatternUtils.verifyTiedProfileChallenge(
credential,
request.userInfo.userId,
@@ -101,7 +100,7 @@ constructor(
lockPatternUtils.verifyGatekeeperPasswordHandle(
pwHandle,
request.operationInfo.gatekeeperChallenge,
- effectiveUserId,
+ request.userInfo.userId,
)
val hat = gkResponse.gatekeeperHAT
lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 008fb26424e2..a164ff483e47 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -126,7 +126,12 @@ constructor(
is PromptKind.Biometric ->
BiometricPromptRequest.Biometric(
info = promptInfo,
- userInfo = BiometricUserInfo(userId = userId),
+ userInfo =
+ BiometricUserInfo(
+ userId = userId,
+ deviceCredentialOwnerId =
+ credentialInteractor.getCredentialOwnerOrSelfId(userId),
+ ),
operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
modalities = kind.activeModalities,
opPackageName = opPackageName,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
index e7a68ac2cfb9..8d5ea3c21e29 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialPasswordViewBinder.kt
@@ -12,6 +12,7 @@ import android.window.OnBackInvokedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.biometrics.ui.CredentialPasswordView
import com.android.systemui.biometrics.ui.CredentialView
import com.android.systemui.biometrics.ui.IPinPad
@@ -21,7 +22,6 @@ import com.android.systemui.res.R
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Sub-binder for the [CredentialPasswordView]. */
object CredentialPasswordViewBinder {
@@ -42,7 +42,7 @@ object CredentialPasswordViewBinder {
view.repeatWhenAttached {
// the header info never changes - do it early
val header = viewModel.header.first()
- passwordField.setTextOperationUser(UserHandle.of(header.user.userIdForPasswordEntry))
+ passwordField.setTextOperationUser(UserHandle.of(header.user.deviceCredentialOwnerId))
viewModel.inputFlags.firstOrNull()?.let { flags -> passwordField.inputType = flags }
if (requestFocusForInput) {
passwordField.requestFocus()
@@ -65,7 +65,7 @@ object CredentialPasswordViewBinder {
if (attestation != null) {
imeManager.hideSoftInputFromWindow(
view.windowToken,
- 0 // flag
+ 0, // flag
)
host.onCredentialMatched(attestation)
} else {
@@ -79,7 +79,7 @@ object CredentialPasswordViewBinder {
launch {
onBackInvokedDispatcher.registerOnBackInvokedCallback(
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- onBackInvokedCallback
+ onBackInvokedCallback,
)
awaitCancellation()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.kt
new file mode 100644
index 000000000000..8bc929985052
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsContent.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.bluetooth.qsdialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.res.R
+
+@Composable
+fun BluetoothDetailsContent() {
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = { context ->
+ // Inflate with the existing dialog xml layout
+ LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)
+ // TODO: b/378513956 - Implement the bluetooth details view
+ },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
index 9dd3b6de423e..ac4d82a95834 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDetailsViewModel.kt
@@ -16,30 +16,11 @@
package com.android.systemui.bluetooth.qsdialog
-import android.view.LayoutInflater
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.plugins.qs.TileDetailsViewModel
-import com.android.systemui.res.R
class BluetoothDetailsViewModel(onLongClick: () -> Unit) : TileDetailsViewModel() {
private val _onLongClick = onLongClick
- @Composable
- override fun GetContentView() {
- AndroidView(
- modifier = Modifier.fillMaxWidth().fillMaxHeight(),
- factory = { context ->
- // Inflate with the existing dialog xml layout
- LayoutInflater.from(context).inflate(R.layout.bluetooth_tile_dialog, null)
- // TODO: b/378513956 - Implement the bluetooth details view
- },
- )
- }
-
override fun clickOnSettingsButton() {
_onLongClick()
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index abd101693b43..4c291a0c5a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -111,6 +111,18 @@ constructor(
@Named(DEFAULT_BACKGROUND_TYPE) private val defaultBackgroundType: CommunalBackgroundType,
) : CommunalSettingsRepository {
+ private val dreamsActivatedOnSleepByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault)
+ }
+
+ private val dreamsActivatedOnDockByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault)
+ }
+
+ private val dreamsActivatedOnPosturedByDefault by lazy {
+ resources.getBoolean(com.android.internal.R.bool.config_dreamsActivatedOnPosturedByDefault)
+ }
+
override fun getFlagEnabled(): Boolean {
return if (getV2FlagEnabled()) {
true
@@ -178,27 +190,27 @@ constructor(
.emitOnStart()
.map {
if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
- 0,
+ dreamsActivatedOnSleepByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_CHARGING
} else if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
- 0,
+ dreamsActivatedOnDockByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_DOCKED
} else if (
- secureSettings.getIntForUser(
+ secureSettings.getBoolForUser(
Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED,
- 0,
+ dreamsActivatedOnPosturedByDefault,
user.id,
- ) == 1
+ )
) {
WhenToDream.WHILE_POSTURED
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 237a19cb31c4..de55c92e84f9 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -545,10 +545,16 @@ constructor(
}
/** CTA tile to be displayed in the glanceable hub (view mode). */
- val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
- communalPrefsInteractor.isCtaDismissed.map { isDismissed ->
- if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
+ val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> by lazy {
+ if (communalSettingsInteractor.isV2FlagEnabled()) {
+ flowOf(listOf<CommunalContentModel.CtaTileInViewMode>())
+ } else {
+ communalPrefsInteractor.isCtaDismissed.map { isDismissed ->
+ if (isDismissed) listOf<CommunalContentModel.CtaTileInViewMode>()
+ else listOf(CommunalContentModel.CtaTileInViewMode())
+ }
}
+ }
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
val tutorialContent: List<CommunalContentModel.Tutorial> =
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index a5f29aa658be..f3eff607dedd 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -122,7 +122,7 @@ constructor(
init {
Log.d(TAG, "Initializing")
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
serviceListing.addCallback(serviceListingCallback)
serviceListing.setListening(true)
serviceListing.reload()
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 5b6859761705..5fc924b14814 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -16,7 +16,6 @@
package com.android.systemui.deviceentry.domain.interactor
-import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.policy.IKeyguardDismissCallback
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
@@ -36,11 +35,9 @@ import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.utils.coroutines.flow.mapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
@@ -285,32 +282,7 @@ constructor(
}
/** Locks the device instantly. */
- fun lockNow() {
- deviceUnlockedInteractor.lockNow()
- }
-
- suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {
- coroutineScope {
- launch {
- isDeviceEntered
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnName = "isDeviceEntered",
- initialValue = isDeviceEntered.value,
- )
- .collect()
- }
-
- launch {
- canSwipeToEnter
- .map { it?.toString() ?: "" }
- .logDiffsForTable(
- tableLogBuffer = tableLogBuffer,
- columnName = "canSwipeToEnter",
- initialValue = canSwipeToEnter.value?.toString() ?: "",
- )
- .collect()
- }
- }
+ fun lockNow(debuggingReason: String) {
+ deviceUnlockedInteractor.lockNow(debuggingReason)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index b1be9a209a0a..c6ae35317c72 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -59,6 +59,7 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.receiveAsFlow
@@ -70,7 +71,7 @@ class DeviceUnlockedInteractor
constructor(
private val authenticationInteractor: AuthenticationInteractor,
private val repository: DeviceEntryRepository,
- trustInteractor: TrustInteractor,
+ private val trustInteractor: TrustInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
@@ -181,7 +182,8 @@ constructor(
val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
repository.deviceUnlockStatus.asStateFlow()
- private val lockNowRequests = Channel<Unit>()
+ /** A [Channel] of "lock now" requests where the values are the debugging reasons. */
+ private val lockNowRequests = Channel<String>()
override suspend fun onActivated(): Nothing {
coroutineScope {
@@ -218,8 +220,8 @@ constructor(
}
/** Locks the device instantly. */
- fun lockNow() {
- lockNowRequests.trySend(Unit)
+ fun lockNow(debuggingReason: String) {
+ lockNowRequests.trySend(debuggingReason)
}
private suspend fun handleLockAndUnlockEvents() {
@@ -244,47 +246,70 @@ constructor(
private suspend fun handleLockEvents() {
merge(
- // Device wakefulness events.
- powerInteractor.detailedWakefulness
- .map { Pair(it.isAsleep(), it.lastSleepReason) }
- .distinctUntilChangedBy { it.first }
- .map { (isAsleep, lastSleepReason) ->
- if (isAsleep) {
- if (
- (lastSleepReason == WakeSleepReason.POWER_BUTTON) &&
- authenticationInteractor.getPowerButtonInstantlyLocks()
- ) {
- LockImmediately("locked instantly from power button")
- } else if (lastSleepReason == WakeSleepReason.SLEEP_BUTTON) {
- LockImmediately("locked instantly from sleep button")
- } else {
- LockWithDelay("entering sleep")
- }
- } else {
- CancelDelayedLock("waking up")
- }
- },
+ trustInteractor.isTrusted.flatMapLatestConflated { isTrusted ->
+ if (isTrusted) {
+ // When entering a trusted environment, power-related lock events are
+ // ignored.
+ Log.d(TAG, "In trusted environment, ignoring power-related lock events")
+ flowOf(CancelDelayedLock("in trusted environment"))
+ } else {
+ // When not in a trusted environment, power-related lock events are treated
+ // as normal.
+ Log.d(
+ TAG,
+ "Not in trusted environment, power-related lock events treated as" +
+ " normal",
+ )
+ merge(
+ // Device wakefulness events.
+ powerInteractor.detailedWakefulness
+ .map { Pair(it.isAsleep(), it.lastSleepReason) }
+ .distinctUntilChangedBy { it.first }
+ .map { (isAsleep, lastSleepReason) ->
+ if (isAsleep) {
+ if (
+ (lastSleepReason == WakeSleepReason.POWER_BUTTON) &&
+ authenticationInteractor
+ .getPowerButtonInstantlyLocks()
+ ) {
+ LockImmediately("locked instantly from power button")
+ } else if (
+ lastSleepReason == WakeSleepReason.SLEEP_BUTTON
+ ) {
+ LockImmediately("locked instantly from sleep button")
+ } else {
+ LockWithDelay("entering sleep")
+ }
+ } else {
+ CancelDelayedLock("waking up")
+ }
+ },
+ // Started dreaming
+ powerInteractor.isInteractive.flatMapLatestConflated { isInteractive ->
+ // Only respond to dream state changes while the device is
+ // interactive.
+ if (isInteractive) {
+ keyguardInteractor.isDreamingAny.distinctUntilChanged().map {
+ isDreaming ->
+ if (isDreaming) {
+ LockWithDelay("started dreaming")
+ } else {
+ CancelDelayedLock("stopped dreaming")
+ }
+ }
+ } else {
+ emptyFlow()
+ }
+ },
+ )
+ }
+ },
// Device enters lockdown.
isInLockdown
.distinctUntilChanged()
.filter { it }
.map { LockImmediately("lockdown") },
- // Started dreaming
- powerInteractor.isInteractive.flatMapLatestConflated { isInteractive ->
- // Only respond to dream state changes while the device is interactive.
- if (isInteractive) {
- keyguardInteractor.isDreamingAny.distinctUntilChanged().map { isDreaming ->
- if (isDreaming) {
- LockWithDelay("started dreaming")
- } else {
- CancelDelayedLock("stopped dreaming")
- }
- }
- } else {
- emptyFlow()
- }
- },
- lockNowRequests.receiveAsFlow().map { LockImmediately("lockNow") },
+ lockNowRequests.receiveAsFlow().map { reason -> LockImmediately(reason) },
)
.collectLatest(::onLockEvent)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 1d36076347bf..c1a59f180225 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -671,7 +671,7 @@ public class KeyguardService extends Service {
checkPermission();
if (SceneContainerFlag.isEnabled()) {
- mDeviceEntryInteractorLazy.get().lockNow();
+ mDeviceEntryInteractorLazy.get().lockNow("doKeyguardTimeout");
} else if (KeyguardWmStateRefactor.isEnabled()) {
mKeyguardServiceLockNowInteractor.onKeyguardServiceDoKeyguardTimeout(options);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 5baef915ea01..fd50485fc3a3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
import static android.provider.Settings.Secure.LOCK_SCREEN_LOCK_AFTER_TIMEOUT;
import static android.provider.Settings.System.LOCKSCREEN_SOUNDS_ENABLED;
@@ -75,6 +76,7 @@ import android.os.Bundle;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
@@ -190,6 +192,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -271,6 +275,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private static final int SYSTEM_READY = 18;
private static final int CANCEL_KEYGUARD_EXIT_ANIM = 19;
private static final int BOOT_INTERACTOR = 20;
+ private static final int BEFORE_USER_SWITCHING = 21;
+ private static final int USER_SWITCHING = 22;
+ private static final int USER_SWITCH_COMPLETE = 23;
/** Enum for reasons behind updating wakeAndUnlock state. */
@Retention(RetentionPolicy.SOURCE)
@@ -288,6 +295,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
int WAKE_AND_UNLOCK = 3;
}
+ private final List<LockNowCallback> mLockNowCallbacks = new ArrayList<>();
+
/**
* The default amount of time we stay awake (used for all key input)
*/
@@ -345,6 +354,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private final ScreenOffAnimationController mScreenOffAnimationController;
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthController;
private final Lazy<ShadeController> mShadeController;
+ /*
+ * Records the user id on request to go away, for validation when WM calls back to start the
+ * exit animation.
+ */
+ private int mGoingAwayRequestedForUserId = -1;
private boolean mSystemReady;
private boolean mBootCompleted;
@@ -352,7 +366,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private boolean mShuttingDown;
private boolean mDozing;
private boolean mAnimatingScreenOff;
- private boolean mIgnoreDismiss;
private final Context mContext;
private final FalsingCollector mFalsingCollector;
@@ -615,41 +628,92 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
- KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+ @VisibleForTesting
+ protected UserTracker.Callback mUserChangedCallback = new UserTracker.Callback() {
@Override
- public void onKeyguardVisibilityChanged(boolean visible) {
- synchronized (KeyguardViewMediator.this) {
- if (!visible && mPendingPinLock) {
- Log.i(TAG, "PIN lock requested, starting keyguard");
+ public void onBeforeUserSwitching(int newUser, @NonNull Runnable resultCallback) {
+ mHandler.sendMessage(mHandler.obtainMessage(BEFORE_USER_SWITCHING,
+ newUser, 0, resultCallback));
+ }
- // Bring the keyguard back in order to show the PIN lock
- mPendingPinLock = false;
- doKeyguardLocked(null);
- }
- }
+ @Override
+ public void onUserChanging(int newUser, @NonNull Context userContext,
+ @NonNull Runnable resultCallback) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCHING,
+ newUser, 0, resultCallback));
}
@Override
- public void onUserSwitching(int userId) {
- Log.d(TAG, String.format("onUserSwitching %d", userId));
- synchronized (KeyguardViewMediator.this) {
- mIgnoreDismiss = true;
- notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
- resetKeyguardDonePendingLocked();
+ public void onUserChanged(int newUser, Context userContext) {
+ mHandler.sendMessage(mHandler.obtainMessage(USER_SWITCH_COMPLETE,
+ newUser, 0));
+ }
+ };
+
+ /**
+ * Handle {@link #BEFORE_USER_SWITCHING}
+ */
+ @VisibleForTesting
+ void handleBeforeUserSwitching(int userId, Runnable resultCallback) {
+ Log.d(TAG, String.format("onBeforeUserSwitching %d", userId));
+ synchronized (KeyguardViewMediator.this) {
+ mHandler.removeMessages(DISMISS);
+ notifyTrustedChangedLocked(mUpdateMonitor.getUserHasTrust(userId));
+ resetKeyguardDonePendingLocked();
+ adjustStatusBarLocked();
+ mKeyguardStateController.notifyKeyguardGoingAway(false);
+ if (mLockPatternUtils.isSecure(userId) && !mShowing) {
+ doKeyguardLocked(null);
+ } else {
resetStateLocked();
- adjustStatusBarLocked();
}
+ resultCallback.run();
}
+ }
- @Override
- public void onUserSwitchComplete(int userId) {
- mIgnoreDismiss = false;
- Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+ /**
+ * Handle {@link #USER_SWITCHING}
+ */
+ @VisibleForTesting
+ void handleUserSwitching(int userId, Runnable resultCallback) {
+ Log.d(TAG, String.format("onUserSwitching %d", userId));
+ synchronized (KeyguardViewMediator.this) {
+ if (!mLockPatternUtils.isSecure(userId)) {
+ dismiss(null, null);
+ }
+ resultCallback.run();
+ }
+ }
+
+ /**
+ * Handle {@link #USER_SWITCH_COMPLETE}
+ */
+ @VisibleForTesting
+ void handleUserSwitchComplete(int userId) {
+ Log.d(TAG, String.format("onUserSwitchComplete %d", userId));
+ // Calling dismiss on a secure user will show the bouncer
+ if (mLockPatternUtils.isSecure(userId)) {
// We are calling dismiss with a delay as there are race conditions in some scenarios
// caused by async layout listeners
mHandler.postDelayed(() -> dismiss(null /* callback */, null /* message */), 500);
}
+ }
+
+ KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible) {
+ synchronized (KeyguardViewMediator.this) {
+ if (!visible && mPendingPinLock) {
+ Log.i(TAG, "PIN lock requested, starting keyguard");
+
+ // Bring the keyguard back in order to show the PIN lock
+ mPendingPinLock = false;
+ doKeyguardLocked(null);
+ }
+ }
+ }
@Override
public void onDeviceProvisioned() {
@@ -1658,7 +1722,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
com.android.internal.R.anim.lock_screen_behind_enter);
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
-
+ mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
+ // start() can be invoked in the middle of user switching, so check for this state and issue
+ // the call manually as that important event was missed.
+ if (mUserTracker.isUserSwitching()) {
+ handleBeforeUserSwitching(mUserTracker.getUserId(), () -> {});
+ handleUserSwitching(mUserTracker.getUserId(), () -> {});
+ }
mJavaAdapter.alwaysCollectFlow(
mWallpaperRepository.getWallpaperSupportsAmbientMode(),
this::setWallpaperSupportsAmbientMode);
@@ -1707,7 +1777,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// System ready can be invoked in the middle of user switching, so check for this state
// and issue the call manually as that important event was missed.
if (mUserTracker.isUserSwitching()) {
- mUpdateCallback.onUserSwitching(mUserTracker.getUserId());
+ mUserChangedCallback.onUserChanging(mUserTracker.getUserId(), mContext, () -> {});
}
}
// Most services aren't available until the system reaches the ready state, so we
@@ -2341,12 +2411,23 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* Enable the keyguard if the settings are appropriate.
*/
private void doKeyguardLocked(Bundle options) {
+ int currentUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (options != null && options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK) != null) {
+ LockNowCallback callback = new LockNowCallback(currentUserId,
+ IRemoteCallback.Stub.asInterface(
+ options.getBinder(LOCK_ON_USER_SWITCH_CALLBACK)));
+ synchronized (mLockNowCallbacks) {
+ mLockNowCallbacks.add(callback);
+ }
+ Log.d(TAG, "LockNowCallback required for user: " + callback.mUserId);
+ }
+
// if another app is disabling us, don't show
if (!mExternallyEnabled
&& !mLockPatternUtils.isUserInLockdown(
mSelectedUserInteractor.getSelectedUserId())) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
-
+ notifyLockNowCallback();
mNeedToReshowWhenReenabled = true;
return;
}
@@ -2364,6 +2445,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// We're removing "reset" in the refactor - "resetting" the views will happen
// as a reaction to the root cause of the "reset" signal.
if (KeyguardWmStateRefactor.isEnabled()) {
+ notifyLockNowCallback();
return;
}
@@ -2376,6 +2458,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
+ "previously hiding. It should be safe to short-circuit "
+ "here.");
resetStateLocked(/* hideBouncer= */ false);
+ notifyLockNowCallback();
return;
}
} else {
@@ -2402,6 +2485,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
Log.d(TAG, "doKeyguard: not showing because device isn't provisioned and the sim is"
+ " not locked or missing");
}
+ notifyLockNowCallback();
return;
}
@@ -2409,6 +2493,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (mLockPatternUtils.isLockScreenDisabled(mSelectedUserInteractor.getSelectedUserId())
&& !lockedOrMissing && !forceShow) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because lockscreen is off");
+ notifyLockNowCallback();
return;
}
@@ -2456,11 +2541,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public void dismiss(IKeyguardDismissCallback callback, CharSequence message) {
- if (mIgnoreDismiss) {
- android.util.Log.i(TAG, "Ignoring request to dismiss (user switch in progress?)");
- return;
- }
-
if (mKeyguardStateController.isKeyguardGoingAway()) {
Log.i(TAG, "Ignoring dismiss because we're already going away.");
return;
@@ -2478,7 +2558,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private void resetStateLocked(boolean hideBouncer) {
- if (DEBUG) Log.e(TAG, "resetStateLocked");
+ if (DEBUG) Log.d(TAG, "resetStateLocked");
Message msg = mHandler.obtainMessage(RESET, hideBouncer ? 1 : 0, 0);
mHandler.sendMessage(msg);
}
@@ -2726,6 +2806,18 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
message = "BOOT_INTERACTOR";
handleBootInteractor();
break;
+ case BEFORE_USER_SWITCHING:
+ message = "BEFORE_USER_SWITCHING";
+ handleBeforeUserSwitching(msg.arg1, (Runnable) msg.obj);
+ break;
+ case USER_SWITCHING:
+ message = "USER_SWITCHING";
+ handleUserSwitching(msg.arg1, (Runnable) msg.obj);
+ break;
+ case USER_SWITCH_COMPLETE:
+ message = "USER_SWITCH_COMPLETE";
+ handleUserSwitchComplete(msg.arg1);
+ break;
}
Log.d(TAG, "KeyguardViewMediator queue processing message: " + message);
}
@@ -2867,6 +2959,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(() -> {
Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ", "
+ reason + ")");
+ if (showing) {
+ notifyLockNowCallback();
+ }
if (KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager if flag is enabled.
@@ -2911,6 +3006,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
synchronized (KeyguardViewMediator.this) {
if (!mSystemReady) {
if (DEBUG) Log.d(TAG, "ignoring handleShow because system is not ready.");
+ notifyLockNowCallback();
return;
}
if (DEBUG) Log.d(TAG, "handleShow");
@@ -2969,12 +3065,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- private final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
+ final Runnable mKeyguardGoingAwayRunnable = new Runnable() {
@SuppressLint("MissingPermission")
@Override
public void run() {
Trace.beginSection("KeyguardViewMediator.mKeyGuardGoingAwayRunnable");
- Log.d(TAG, "keyguardGoingAwayRunnable");
mKeyguardViewControllerLazy.get().keyguardGoingAway();
int flags = 0;
@@ -3011,6 +3106,10 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// Handled in WmLockscreenVisibilityManager if flag is enabled.
if (!KeyguardWmStateRefactor.isEnabled()) {
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ Log.d(TAG, "keyguardGoingAway requested for userId: "
+ + mGoingAwayRequestedForUserId);
+
// Don't actually hide the Keyguard at the moment, wait for window manager
// until it tells us it's safe to do so with startKeyguardExitAnimation.
// Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager
@@ -3149,6 +3248,30 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) {
Log.d(TAG, "handleStartKeyguardExitAnimation startTime=" + startTime
+ " fadeoutDuration=" + fadeoutDuration);
+ int currentUserId = mSelectedUserInteractor.getSelectedUserId();
+ if (!KeyguardWmStateRefactor.isEnabled() && mGoingAwayRequestedForUserId != currentUserId) {
+ Log.e(TAG, "Not executing handleStartKeyguardExitAnimationInner() due to userId "
+ + "mismatch. Requested: " + mGoingAwayRequestedForUserId + ", current: "
+ + currentUserId);
+ if (finishedCallback != null) {
+ // There will not execute animation, send a finish callback to ensure the remote
+ // animation won't hang there.
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to call onAnimationFinished", e);
+ }
+ }
+ mHiding = false;
+ if (mLockPatternUtils.isSecure(currentUserId)) {
+ doKeyguardLocked(null);
+ } else {
+ resetStateLocked();
+ dismiss(null, null);
+ }
+ return;
+ }
+
synchronized (KeyguardViewMediator.this) {
mIsKeyguardExitAnimationCanceled = false;
// Tell ActivityManager that we canceled the keyguard animation if
@@ -3393,6 +3516,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
* app transition before finishing the current RemoteAnimation, or the keyguard being re-shown).
*/
private void handleCancelKeyguardExitAnimation() {
+ if (!KeyguardWmStateRefactor.isEnabled()
+ && mGoingAwayRequestedForUserId != mSelectedUserInteractor.getSelectedUserId()) {
+ Log.e(TAG, "Setting pendingLock = true due to userId mismatch. Requested: "
+ + mGoingAwayRequestedForUserId + ", current: "
+ + mSelectedUserInteractor.getSelectedUserId());
+ setPendingLock(true);
+ }
if (mPendingLock) {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
+ "There's a pending lock, so we were cancelled because the device was locked "
@@ -3493,6 +3623,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mSurfaceBehindRemoteAnimationRequested = true;
if (ENABLE_NEW_KEYGUARD_SHELL_TRANSITIONS && !KeyguardWmStateRefactor.isEnabled()) {
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */);
return;
}
@@ -3513,6 +3644,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (!KeyguardWmStateRefactor.isEnabled()) {
// Handled in WmLockscreenVisibilityManager.
+ mGoingAwayRequestedForUserId = mSelectedUserInteractor.getSelectedUserId();
+ Log.d(TAG, "keyguardGoingAway requested for userId: "
+ + mGoingAwayRequestedForUserId);
mActivityTaskManagerService.keyguardGoingAway(flags);
}
} catch (RemoteException e) {
@@ -3966,6 +4100,29 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mUiBgExecutor.execute(mTrustManager::reportKeyguardShowingChanged);
}
+ private void notifyLockNowCallback() {
+ List<LockNowCallback> callbacks;
+ synchronized (mLockNowCallbacks) {
+ callbacks = new ArrayList<LockNowCallback>(mLockNowCallbacks);
+ mLockNowCallbacks.clear();
+ }
+ Iterator<LockNowCallback> iter = callbacks.listIterator();
+ while (iter.hasNext()) {
+ LockNowCallback callback = iter.next();
+ iter.remove();
+ if (callback.mUserId != mSelectedUserInteractor.getSelectedUserId()) {
+ Log.i(TAG, "Not notifying lockNowCallback due to user mismatch");
+ continue;
+ }
+ Log.i(TAG, "Notifying lockNowCallback");
+ try {
+ callback.mRemoteCallback.sendResult(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not issue LockNowCallback sendResult", e);
+ }
+ }
+ }
+
private void notifyTrustedChangedLocked(boolean trusted) {
int size = mKeyguardStateCallbacks.size();
for (int i = size - 1; i >= 0; i--) {
@@ -4130,4 +4287,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
};
}
+
+ private class LockNowCallback {
+ final int mUserId;
+ final IRemoteCallback mRemoteCallback;
+
+ LockNowCallback(int userId, IRemoteCallback remoteCallback) {
+ mUserId = userId;
+ mRemoteCallback = remoteCallback;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index aa28c5b90090..a9729eaad266 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -124,7 +124,7 @@ constructor(
init {
legacySettingSyncer.startSyncing()
- dumpManager.registerDumpable("KeyguardQuickAffordances", Dumpster())
+ dumpManager.registerNormalDumpable("KeyguardQuickAffordances", Dumpster())
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 621cc4666d31..aaad10140a92 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -326,10 +326,7 @@ interface KeyguardRepository {
fun setShortcutAbsoluteTop(top: Float)
- /**
- * Set bottom of notifications from notification stack, and Magic Portrait will layout base on
- * this value
- */
+ /** Set bottom of notifications from notification stack */
fun setNotificationStackAbsoluteBottom(bottom: Float)
fun setWallpaperFocalAreaBounds(bounds: RectF)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
index 934afe248a36..9c744d63a093 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WallpaperFocalAreaInteractor.kt
@@ -50,8 +50,7 @@ constructor(
keyguardClockRepository: KeyguardClockRepository,
wallpaperRepository: WallpaperRepository,
) {
- // When there's notifications in splitshade, magic portrait shape effects should be left
- // aligned in foldable
+ // When there's notifications in splitshade, the focal area shape effect should be left aligned
private val notificationInShadeWideLayout: Flow<Boolean> =
combine(
shadeRepository.isShadeLayoutWide,
@@ -104,7 +103,7 @@ constructor(
)
val (left, right) =
// tablet landscape
- if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
Pair(
scaledBounds.centerX() - maxFocalAreaWidth / 2F,
scaledBounds.centerX() + maxFocalAreaWidth / 2F,
@@ -129,7 +128,7 @@ constructor(
wallpaperZoomedInScale
val top =
// tablet landscape
- if (context.resources.getBoolean(R.bool.center_align_magic_portrait_shape)) {
+ if (context.resources.getBoolean(R.bool.center_align_focal_area_shape)) {
// no strict constraints for top, use bottom margin to make it symmetric
// vertically
scaledBounds.top + scaledBottomMargin
@@ -169,8 +168,8 @@ constructor(
)
}
- // A max width for magic portrait shape effects bounds, to avoid it going too large
- // in large screen portrait mode
+ // A max width for focal area shape effects bounds, to avoid
+ // it becoming too large in large screen portrait mode
const val FOCAL_AREA_MAX_WIDTH_DP = 500
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 856e1d6ffdb7..8b213be02969 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AccessibilityActionsSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
@@ -58,6 +59,7 @@ constructor(
defaultStatusBarSection: DefaultStatusBarSection,
splitShadeNotificationStackScrollLayoutSection: SplitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines: SplitShadeGuidelines,
+ aodPromotedNotificationSection: AodPromotedNotificationSection,
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
clockSection: ClockSection,
@@ -76,6 +78,7 @@ constructor(
defaultStatusBarSection,
splitShadeNotificationStackScrollLayoutSection,
splitShadeGuidelines,
+ aodPromotedNotificationSection,
aodNotificationIconsSection,
smartspaceSection,
aodBurnInSection,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index ed1bdb0e2922..6c9a755148e9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -26,6 +26,7 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.res.R
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
@@ -36,10 +37,15 @@ class AodPromotedNotificationSection
@Inject
constructor(
private val viewModelFactory: AODPromotedNotificationViewModel.Factory,
+ private val shadeInteractor: ShadeInteractor,
private val logger: PromotedNotificationLogger,
) : KeyguardSection() {
var view: ComposeView? = null
+ init {
+ logger.logSectionCreated(this)
+ }
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!PromotedNotificationUiAod.isEnabled) {
return
@@ -54,7 +60,7 @@ constructor(
constraintLayout.addView(this)
}
- logger.logSectionAddedViews()
+ logger.logSectionAddedViews(this)
}
override fun bindData(constraintLayout: ConstraintLayout) {
@@ -66,7 +72,7 @@ constructor(
// Do nothing; the binding happens in the AODPromotedNotification Composable.
- logger.logSectionBoundData()
+ logger.logSectionBoundData(this)
}
override fun applyConstraints(constraintSet: ConstraintSet) {
@@ -74,18 +80,22 @@ constructor(
return
}
- checkNotNull(view)
+ // view may have been created by a different instance of the section (!), and we don't
+ // actually *need* it to set constraints, so don't check for it here.
constraintSet.apply {
+ val isShadeLayoutWide = shadeInteractor.isShadeLayoutWide.value
+ val endGuidelineId = if (isShadeLayoutWide) R.id.split_shade_guideline else PARENT_ID
+
connect(viewId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, 0)
connect(viewId, START, PARENT_ID, START, 0)
- connect(viewId, END, PARENT_ID, END, 0)
+ connect(viewId, END, endGuidelineId, END, 0)
constrainWidth(viewId, ConstraintSet.MATCH_CONSTRAINT)
constrainHeight(viewId, ConstraintSet.WRAP_CONTENT)
}
- logger.logSectionAppliedConstraints()
+ logger.logSectionAppliedConstraints(this)
}
override fun removeViews(constraintLayout: ConstraintLayout) {
@@ -97,7 +107,7 @@ constructor(
view = null
- logger.logSectionRemovedViews()
+ logger.logSectionRemovedViews(this)
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
index 3eac12d2b24c..ea5f81c75405 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImpl.kt
@@ -306,7 +306,7 @@ class LegacyMediaDataManagerImpl(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
// Initialize the internal processing pipeline. The listeners at the front of the pipeline
// are set as internal listeners so that they receive events. From there, events are
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
index ad84a5eb74ab..0a70e78bf039 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/resume/MediaResumeListener.kt
@@ -141,7 +141,7 @@ constructor(
init {
if (useMediaResumption) {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
val unlockFilter = IntentFilter()
unlockFilter.addAction(Intent.ACTION_USER_UNLOCKED)
broadcastDispatcher.registerReceiver(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index f05029b2d55f..c604bfb2d5c1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -343,7 +343,7 @@ constructor(
.stateIn(applicationScope, SharingStarted.Eagerly, true)
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
mediaFrame = inflateMediaCarousel()
mediaCarousel = mediaFrame.requireViewById(R.id.media_carousel_scroller)
pageIndicator = mediaFrame.requireViewById(R.id.media_page_indicator)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
index 7803f229dda7..d803ebed94e3 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/notetask/INoteTaskBubblesService.aidl
@@ -26,6 +26,6 @@ interface INoteTaskBubblesService {
boolean areBubblesAvailable();
- void showOrHideAppBubble(in Intent intent, in UserHandle userHandle, in Icon icon,
+ void showOrHideNoteBubble(in Intent intent, in UserHandle userHandle, in Icon icon,
in NoteTaskBubbleExpandBehavior bubbleExpandBehavior);
}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
index 169285f6742e..e20ccfa13e17 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskBubblesController.kt
@@ -76,8 +76,8 @@ constructor(
}
}
- /** Calls the [Bubbles.showOrHideAppBubble] API as [UserHandle.USER_SYSTEM]. */
- open suspend fun showOrHideAppBubble(
+ /** Calls the [Bubbles.showOrHideNoteBubble] API as [UserHandle.USER_SYSTEM]. */
+ open suspend fun showOrHideNoteBubble(
intent: Intent,
userHandle: UserHandle,
icon: Icon,
@@ -85,7 +85,7 @@ constructor(
) {
withContext(bgDispatcher) {
serviceConnector
- .post { it.showOrHideAppBubble(intent, userHandle, icon, bubbleExpandBehavior) }
+ .post { it.showOrHideNoteBubble(intent, userHandle, icon, bubbleExpandBehavior) }
.whenComplete { _, error ->
if (error != null) {
debugLog(error = error) {
@@ -119,7 +119,7 @@ constructor(
return object : INoteTaskBubblesService.Stub() {
override fun areBubblesAvailable() = mOptionalBubbles.isPresent
- override fun showOrHideAppBubble(
+ override fun showOrHideNoteBubble(
intent: Intent,
userHandle: UserHandle,
icon: Icon,
@@ -131,12 +131,12 @@ constructor(
bubbleExpandBehavior ==
NoteTaskBubbleExpandBehavior.KEEP_IF_EXPANDED &&
bubbles.isBubbleExpanded(
- Bubble.getAppBubbleKeyForApp(intent.`package`, userHandle)
+ Bubble.getNoteBubbleKeyForApp(intent.`package`, userHandle)
)
) {
return@ifPresentOrElse
}
- bubbles.showOrHideAppBubble(intent, userHandle, icon)
+ bubbles.showOrHideNoteBubble(intent, userHandle, icon)
},
{
debugLog {
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index ad1f37070c9d..7e0128ab8f13 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -96,7 +96,7 @@ constructor(
val info = infoReference.getAndSet(null) ?: return
- if (key != Bubble.getAppBubbleKeyForApp(info.packageName, info.user)) return
+ if (key != Bubble.getNoteBubbleKeyForApp(info.packageName, info.user)) return
// Safe guard mechanism, this callback should only be called for app bubbles.
if (info.launchMode !is NoteTaskLaunchMode.AppBubble) return
@@ -219,7 +219,7 @@ constructor(
val intent = createNoteTaskIntent(info, useStylusMode)
val icon =
Icon.createWithResource(context, R.drawable.ic_note_task_shortcut_widget)
- noteTaskBubblesController.showOrHideAppBubble(
+ noteTaskBubblesController.showOrHideNoteBubble(
intent,
user,
icon,
diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
index f8d442de0f55..25d53e6d1f1f 100644
--- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt
@@ -50,7 +50,7 @@ constructor(
@FalsingCollectorActual private val falsingCollector: FalsingCollector,
private val screenOffAnimationController: ScreenOffAnimationController,
private val statusBarStateController: StatusBarStateController,
- private val cameraGestureHelper: Provider<CameraGestureHelper>,
+ private val cameraGestureHelper: Provider<CameraGestureHelper?>,
) {
/** Whether the screen is on or off. */
val isInteractive: Flow<Boolean> = repository.isInteractive
@@ -154,8 +154,9 @@ constructor(
// or onFinishedGoingToSleep(), carry that state forward. It will be reset by the next
// onStartedGoingToSleep.
val powerButtonLaunchGestureTriggered =
- powerButtonLaunchGestureTriggeredOnWakeUp ||
- repository.wakefulness.value.powerButtonLaunchGestureTriggered
+ !isPowerButtonGestureSuppressed() &&
+ (powerButtonLaunchGestureTriggeredOnWakeUp ||
+ repository.wakefulness.value.powerButtonLaunchGestureTriggered)
repository.updateWakefulness(
rawState = WakefulnessState.STARTING_TO_WAKE,
@@ -204,8 +205,9 @@ constructor(
// If the launch gesture was previously detected via onCameraLaunchGestureDetected, carry
// that state forward. It will be reset by the next onStartedGoingToSleep.
val powerButtonLaunchGestureTriggered =
- powerButtonLaunchGestureTriggeredDuringSleep ||
- repository.wakefulness.value.powerButtonLaunchGestureTriggered
+ !isPowerButtonGestureSuppressed() &&
+ (powerButtonLaunchGestureTriggeredDuringSleep ||
+ repository.wakefulness.value.powerButtonLaunchGestureTriggered)
repository.updateWakefulness(
rawState = WakefulnessState.ASLEEP,
@@ -218,11 +220,7 @@ constructor(
}
fun onCameraLaunchGestureDetected() {
- if (
- cameraGestureHelper
- .get()
- .canCameraGestureBeLaunched(statusBarStateController.getState())
- ) {
+ if (!isPowerButtonGestureSuppressed()) {
repository.updateWakefulness(powerButtonLaunchGestureTriggered = true)
}
}
@@ -240,6 +238,16 @@ constructor(
.collect()
}
+ /**
+ * Whether the power button gesture isn't allowed to launch anything even if a double tap is
+ * detected.
+ */
+ private fun isPowerButtonGestureSuppressed(): Boolean {
+ return cameraGestureHelper
+ .get()
+ ?.canCameraGestureBeLaunched(statusBarStateController.state) == false
+ }
+
companion object {
private const val FSI_WAKE_WHY = "full_screen_intent"
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
index 67d390d4f10d..79a513f24995 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt
@@ -85,7 +85,7 @@ class PrivacyConfig @Inject constructor(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
deviceConfigProxy.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_PRIVACY,
uiExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
index eb8ef9bf3e50..f9a7205c56f5 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt
@@ -98,7 +98,7 @@ class PrivacyItemController @Inject constructor(
}
init {
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
privacyConfig.addCallback(optionsCallback)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
index 30fb50db82a2..1233a2f285d5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/TileDetails.kt
@@ -38,8 +38,17 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsContent
+import com.android.systemui.bluetooth.qsdialog.BluetoothDetailsViewModel
+import com.android.systemui.plugins.qs.TileDetailsViewModel
import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.qs.panels.ui.viewmodel.DetailsViewModel
+import com.android.systemui.qs.tiles.dialog.InternetDetailsContent
+import com.android.systemui.qs.tiles.dialog.InternetDetailsViewModel
+import com.android.systemui.qs.tiles.dialog.ModesDetailsContent
+import com.android.systemui.qs.tiles.dialog.ModesDetailsViewModel
+import com.android.systemui.qs.tiles.dialog.ScreenRecordDetailsContent
+import com.android.systemui.qs.tiles.dialog.ScreenRecordDetailsViewModel
@Composable
fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewModel) {
@@ -107,7 +116,17 @@ fun TileDetails(modifier: Modifier = Modifier, detailsViewModel: DetailsViewMode
style = MaterialTheme.typography.titleSmall,
)
}
- tileDetailedViewModel.GetContentView()
+ MapTileDetailsContent(tileDetailedViewModel)
+ }
+}
+
+@Composable
+private fun MapTileDetailsContent(tileDetailsViewModel: TileDetailsViewModel) {
+ when (tileDetailsViewModel) {
+ is InternetDetailsViewModel -> InternetDetailsContent(tileDetailsViewModel)
+ is ScreenRecordDetailsViewModel -> ScreenRecordDetailsContent(tileDetailsViewModel)
+ is BluetoothDetailsViewModel -> BluetoothDetailsContent()
+ is ModesDetailsViewModel -> ModesDetailsContent(tileDetailsViewModel)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt
new file mode 100644
index 000000000000..7d396c58630e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContent.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.res.R
+
+@Composable
+fun InternetDetailsContent(viewModel: InternetDetailsViewModel) {
+ val coroutineScope = rememberCoroutineScope()
+ val context = LocalContext.current
+
+ val internetDetailsContentManager = remember {
+ viewModel.contentManagerFactory.create(
+ canConfigMobileData = viewModel.getCanConfigMobileData(),
+ canConfigWifi = viewModel.getCanConfigWifi(),
+ coroutineScope = coroutineScope,
+ context = context,
+ )
+ }
+
+ AndroidView(
+ modifier = Modifier.fillMaxSize(),
+ factory = { context ->
+ // Inflate with the existing dialog xml layout and bind it with the manager
+ val view =
+ LayoutInflater.from(context).inflate(R.layout.internet_connectivity_dialog, null)
+ internetDetailsContentManager.bind(view)
+
+ view
+ // TODO: b/377388104 - Polish the internet details view UI
+ },
+ onRelease = { internetDetailsContentManager.unBind() },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
index df4dddbca9e6..0ed56f62ee6c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
@@ -16,18 +16,7 @@
package com.android.systemui.qs.tiles.dialog
-import android.util.Log
-import android.view.LayoutInflater
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.plugins.qs.TileDetailsViewModel
-import com.android.systemui.res.R
import com.android.systemui.statusbar.connectivity.AccessPointController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -37,45 +26,9 @@ class InternetDetailsViewModel
@AssistedInject
constructor(
private val accessPointController: AccessPointController,
- private val contentManagerFactory: InternetDetailsContentManager.Factory,
+ val contentManagerFactory: InternetDetailsContentManager.Factory,
@Assisted private val onLongClick: () -> Unit,
) : TileDetailsViewModel() {
- private lateinit var internetDetailsContentManager: InternetDetailsContentManager
-
- @Composable
- override fun GetContentView() {
- val coroutineScope = rememberCoroutineScope()
- val context = LocalContext.current
-
- internetDetailsContentManager = remember {
- contentManagerFactory.create(
- canConfigMobileData = accessPointController.canConfigMobileData(),
- canConfigWifi = accessPointController.canConfigWifi(),
- coroutineScope = coroutineScope,
- context = context,
- )
- }
- AndroidView(
- modifier = Modifier.fillMaxWidth().fillMaxHeight(),
- factory = { context ->
- // Inflate with the existing dialog xml layout and bind it with the manager
- val view =
- LayoutInflater.from(context)
- .inflate(R.layout.internet_connectivity_dialog, null)
- internetDetailsContentManager.bind(view)
-
- view
- // TODO: b/377388104 - Polish the internet details view UI
- },
- onRelease = {
- internetDetailsContentManager.unBind()
- if (DEBUG) {
- Log.d(TAG, "onRelease")
- }
- },
- )
- }
-
override fun clickOnSettingsButton() {
onLongClick()
}
@@ -96,13 +49,16 @@ constructor(
return "Tab a network to connect"
}
+ fun getCanConfigMobileData(): Boolean {
+ return accessPointController.canConfigMobileData()
+ }
+
+ fun getCanConfigWifi(): Boolean {
+ return accessPointController.canConfigWifi()
+ }
+
@AssistedFactory
interface Factory {
fun create(onLongClick: () -> Unit): InternetDetailsViewModel
}
-
- companion object {
- private const val TAG = "InternetDetailsVModel"
- private val DEBUG: Boolean = Log.isLoggable(TAG, Log.DEBUG)
- }
}
diff --git a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsContent.kt
index 90965092ac2b..c5ecaffdf188 100644
--- a/packages/CrashRecovery/framework/java/android/service/watchdog/IExplicitHealthCheckService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsContent.kt
@@ -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,13 @@
* limitations under the License.
*/
-package android.service.watchdog;
+package com.android.systemui.qs.tiles.dialog
-import android.os.RemoteCallback;
+import androidx.compose.runtime.Composable
+import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
-/**
- * @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);
+@Composable
+fun ModesDetailsContent(viewModel: ModesDetailsViewModel) {
+ // TODO(b/378513940): Finish implementing this function.
+ ModeTileGrid(viewModel = viewModel.viewModel)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
index 511597d05d37..9a39c3c095ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ModesDetailsViewModel.kt
@@ -16,22 +16,14 @@
package com.android.systemui.qs.tiles.dialog
-import androidx.compose.runtime.Composable
import com.android.systemui.plugins.qs.TileDetailsViewModel
-import com.android.systemui.statusbar.policy.ui.dialog.composable.ModeTileGrid
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
/** The view model used for the modes details view in the Quick Settings */
class ModesDetailsViewModel(
private val onSettingsClick: () -> Unit,
- private val viewModel: ModesDialogViewModel,
+ val viewModel: ModesDialogViewModel,
) : TileDetailsViewModel() {
- @Composable
- override fun GetContentView() {
- // TODO(b/378513940): Finish implementing this function.
- ModeTileGrid(viewModel = viewModel)
- }
-
override fun clickOnSettingsButton() {
onSettingsClick()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt
new file mode 100644
index 000000000000..bf1a51d8cd59
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsContent.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.view.LayoutInflater
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.systemui.res.R
+import com.android.systemui.screenrecord.ScreenRecordPermissionViewBinder
+
+@Composable
+fun ScreenRecordDetailsContent(viewModel: ScreenRecordDetailsViewModel) {
+ // TODO(b/378514312): Finish implementing this function.
+
+ if (viewModel.recordingController.isScreenCaptureDisabled) {
+ // TODO(b/388345506): Show disabled page here.
+ return
+ }
+
+ val viewBinder: ScreenRecordPermissionViewBinder = remember {
+ viewModel.recordingController.createScreenRecordPermissionViewBinder(
+ viewModel.onStartRecordingClicked
+ )
+ }
+
+ AndroidView(
+ modifier = Modifier.fillMaxWidth().fillMaxHeight(),
+ factory = { context ->
+ // Inflate with the existing dialog xml layout
+ val view = LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null)
+ viewBinder.bind(view)
+
+ view
+ // TODO(b/378514473): Revamp the details view according to the spec.
+ },
+ onRelease = { viewBinder.unbind() },
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
index 54e4a521c239..c84ddb6fdb36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/ScreenRecordDetailsViewModel.kt
@@ -16,49 +16,15 @@
package com.android.systemui.qs.tiles.dialog
-import android.view.LayoutInflater
-import androidx.compose.foundation.layout.fillMaxHeight
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.viewinterop.AndroidView
import com.android.systemui.plugins.qs.TileDetailsViewModel
-import com.android.systemui.res.R
import com.android.systemui.screenrecord.RecordingController
-import com.android.systemui.screenrecord.ScreenRecordPermissionViewBinder
/** The view model used for the screen record details view in the Quick Settings */
class ScreenRecordDetailsViewModel(
- private val recordingController: RecordingController,
- private val onStartRecordingClicked: Runnable,
+ val recordingController: RecordingController,
+ val onStartRecordingClicked: Runnable,
) : TileDetailsViewModel() {
- private var viewBinder: ScreenRecordPermissionViewBinder =
- recordingController.createScreenRecordPermissionViewBinder(onStartRecordingClicked)
-
- @Composable
- override fun GetContentView() {
- // TODO(b/378514312): Finish implementing this function.
-
- if (recordingController.isScreenCaptureDisabled) {
- // TODO(b/388345506): Show disabled page here.
- return
- }
-
- AndroidView(
- modifier = Modifier.fillMaxWidth().fillMaxHeight(),
- factory = { context ->
- // Inflate with the existing dialog xml layout
- val view = LayoutInflater.from(context).inflate(R.layout.screen_share_dialog, null)
- viewBinder.bind(view)
-
- view
- // TODO(b/378514473): Revamp the details view according to the spec.
- },
- onRelease = { viewBinder.unbind() },
- )
- }
-
override fun clickOnSettingsButton() {
// No settings button in this tile.
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
index e36e40d312b5..3604c4cc4cfd 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt
@@ -32,7 +32,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -90,29 +89,14 @@ interface KeyguardlessSceneContainerFrameworkModule {
@Provides
fun containerConfig(): SceneContainerConfig {
return SceneContainerConfig(
- // Note that this list is in z-order. The first one is the bottom-most and the
- // last one is top-most.
- sceneKeys =
- listOfNotNull(
- Scenes.Gone,
- Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
- Scenes.Shade.takeUnless { DualShade.isEnabled },
- ),
+ // Note that this list is in z-order. The first one is the bottom-most and the last
+ // one is top-most.
+ sceneKeys = listOf(Scenes.Gone, Scenes.QuickSettings, Scenes.Shade),
initialSceneKey = Scenes.Gone,
transitions = SceneContainerTransitions,
- overlayKeys =
- listOfNotNull(
- Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
- Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
- ),
+ overlayKeys = listOf(Overlays.NotificationsShade, Overlays.QuickSettingsShade),
navigationDistances =
- mapOf(
- Scenes.Gone to 0,
- Scenes.Shade to 1.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 2.takeUnless { DualShade.isEnabled },
- )
- .filterValues { it != null }
- .mapValues { checkNotNull(it.value) },
+ mapOf(Scenes.Gone to 0, Scenes.Shade to 1, Scenes.QuickSettings to 2),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
index fe014524e3da..91677b03d589 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt
@@ -32,7 +32,6 @@ import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
import com.android.systemui.scene.ui.viewmodel.SplitEdgeDetector
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.shared.flag.DualShade
import dagger.Binds
import dagger.Module
import dagger.Provides
@@ -103,28 +102,24 @@ interface SceneContainerFrameworkModule {
Scenes.Dream,
Scenes.Lockscreen,
Scenes.Bouncer,
- Scenes.QuickSettings.takeUnless { DualShade.isEnabled },
- Scenes.Shade.takeUnless { DualShade.isEnabled },
+ Scenes.QuickSettings,
+ Scenes.Shade,
),
initialSceneKey = Scenes.Lockscreen,
transitions = SceneContainerTransitions,
overlayKeys =
- listOfNotNull(
- Overlays.NotificationsShade.takeIf { DualShade.isEnabled },
- Overlays.QuickSettingsShade.takeIf { DualShade.isEnabled },
- ),
+ listOfNotNull(Overlays.NotificationsShade, Overlays.QuickSettingsShade),
navigationDistances =
mapOf(
Scenes.Gone to 0,
Scenes.Lockscreen to 0,
Scenes.Communal to 1,
Scenes.Dream to 2,
- Scenes.Shade to 3.takeUnless { DualShade.isEnabled },
- Scenes.QuickSettings to 4.takeUnless { DualShade.isEnabled },
+ Scenes.Shade to 3,
+ Scenes.QuickSettings to 4,
Scenes.Bouncer to 5,
)
- .filterValues { it != null }
- .mapValues { checkNotNull(it.value) },
+ .mapValues { it.value },
)
}
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 2fd584176220..94e32fcb9ac6 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
@@ -44,6 +44,7 @@ import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.TrustInteractor
import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardScenes
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.model.SceneContainerPlugin
@@ -148,6 +149,7 @@ constructor(
private val activityTransitionAnimator: ActivityTransitionAnimator,
private val shadeModeInteractor: ShadeModeInteractor,
@SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer,
+ private val trustInteractor: TrustInteractor,
) : CoreStartable {
private val centralSurfaces: CentralSurfaces?
get() = centralSurfacesOptLazy.get().getOrNull()
@@ -173,6 +175,7 @@ constructor(
notifyKeyguardDismissCancelledCallbacks()
refreshLockscreenEnabled()
hydrateActivityTransitionAnimationState()
+ lockWhenDeviceBecomesUntrusted()
} else {
sceneLogger.logFrameworkEnabled(
isEnabled = false,
@@ -998,6 +1001,18 @@ constructor(
)
}
+ private fun lockWhenDeviceBecomesUntrusted() {
+ applicationScope.launch {
+ trustInteractor.isTrusted.pairwise().collect { (wasTrusted, isTrusted) ->
+ if (wasTrusted && !isTrusted && !deviceEntryInteractor.isDeviceEntered.value) {
+ deviceEntryInteractor.lockNow(
+ "Exited trusted environment while not device not entered"
+ )
+ }
+ }
+ }
+ }
+
companion object {
private const val TAG = "SceneContainerStartable"
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
index 3bca4e421cbd..1a91afcf6cac 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java
@@ -152,8 +152,8 @@ public class AppClipsService extends Service {
return CAPTURE_CONTENT_FOR_NOTE_FAILED;
}
- if (!mOptionalBubbles.get().isAppBubbleTaskId(taskId)) {
- Log.d(TAG, String.format("Taskid %d is not app bubble task", taskId));
+ if (!mOptionalBubbles.get().isNoteBubbleTaskId(taskId)) {
+ Log.d(TAG, String.format("Taskid %d is not note bubble task", taskId));
return CAPTURE_CONTENT_FOR_NOTE_WINDOW_MODE_UNSUPPORTED;
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index a48d4d4d3b5f..1f02d5a2d0bc 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -151,7 +151,7 @@ internal constructor(
registerUserSwitchObserver()
- dumpManager.registerDumpable(TAG, this)
+ dumpManager.registerNormalDumpable(TAG, this)
}
override fun onReceive(context: Context, intent: Intent) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index e38e53d67f61..e5349deaa811 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -43,7 +43,7 @@ constructor(
shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
onPanelExpansionChanged(currentState)
shadeExpansionStateManager.addStateListener(this::onPanelStateChanged)
- dumpManager.registerDumpable(
+ dumpManager.registerNormalDumpable(
ScrimShadeTransitionController::class.java.simpleName,
this::dump
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
index 541a07c47df2..98b75216bbe9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModel.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.content.Context
import android.view.View
import com.android.internal.jank.Cuj
import com.android.systemui.animation.ActivityTransitionAnimator
@@ -23,6 +24,7 @@ import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.plugins.ActivityStarter
@@ -52,6 +54,7 @@ import kotlinx.coroutines.flow.stateIn
open class CallChipViewModel
@Inject
constructor(
+ @Main private val context: Context,
@Application private val scope: CoroutineScope,
interactor: CallChipInteractor,
systemClock: SystemClock,
@@ -65,15 +68,18 @@ constructor(
is OngoingCallModel.NoCall,
is OngoingCallModel.InCallWithVisibleApp -> OngoingActivityChipModel.Hidden()
is OngoingCallModel.InCall -> {
+ val contentDescription = getContentDescription(state.appName)
val icon =
if (state.notificationIconView != null) {
StatusBarConnectedDisplays.assertInLegacyMode()
OngoingActivityChipModel.ChipIcon.StatusBarView(
- state.notificationIconView
+ state.notificationIconView,
+ contentDescription,
)
} else if (StatusBarConnectedDisplays.isEnabled) {
OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
- state.notificationKey
+ state.notificationKey,
+ contentDescription,
)
} else {
OngoingActivityChipModel.ChipIcon.SingleColorIcon(phoneIcon)
@@ -155,6 +161,17 @@ constructor(
)
}
+ private fun getContentDescription(appName: String): ContentDescription {
+ val ongoingCallDescription = context.getString(R.string.ongoing_call_content_description)
+ return ContentDescription.Loaded(
+ context.getString(
+ R.string.accessibility_desc_notification_icon,
+ appName,
+ ongoingCallDescription,
+ )
+ )
+ }
+
companion object {
private val phoneIcon =
Icon.Resource(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
index cece52110567..a9338885d4c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractor.kt
@@ -138,7 +138,7 @@ constructor(
}
}
- return NotificationChipModel(key, statusBarChipIconView, promotedContent)
+ return NotificationChipModel(key, appName, statusBarChipIconView, promotedContent)
}
@AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
index c6759da304bb..e7a90804a768 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/domain/model/NotificationChipModel.kt
@@ -22,6 +22,8 @@ import com.android.systemui.statusbar.notification.promoted.shared.model.Promote
/** Modeling all the data needed to render a status bar notification chip. */
data class NotificationChipModel(
val key: String,
+ /** The user-readable name of the app that posted the call notification. */
+ val appName: String,
val statusBarChipIconView: StatusBarIconView?,
val promotedContent: PromotedNotificationContentModel,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
index 46456b841e3f..b0da6428f579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt
@@ -16,10 +16,14 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.Context
import android.view.View
import com.android.systemui.Flags
+import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.notification.domain.interactor.StatusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.domain.model.NotificationChipModel
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -43,6 +47,7 @@ import kotlinx.coroutines.launch
class NotifChipsViewModel
@Inject
constructor(
+ @Main private val context: Context,
@Application private val applicationScope: CoroutineScope,
private val notifChipsInteractor: StatusBarNotificationChipsInteractor,
headsUpNotificationInteractor: HeadsUpNotificationInteractor,
@@ -65,13 +70,20 @@ constructor(
headsUpState: TopPinnedState
): OngoingActivityChipModel.Shown {
StatusBarNotifChips.assertInNewMode()
+ val contentDescription = getContentDescription(this.appName)
val icon =
if (this.statusBarChipIconView != null) {
StatusBarConnectedDisplays.assertInLegacyMode()
- OngoingActivityChipModel.ChipIcon.StatusBarView(this.statusBarChipIconView)
+ OngoingActivityChipModel.ChipIcon.StatusBarView(
+ this.statusBarChipIconView,
+ contentDescription,
+ )
} else {
StatusBarConnectedDisplays.assertInNewMode()
- OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(this.key)
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(
+ this.key,
+ contentDescription,
+ )
}
val colors = this.promotedContent.toCustomColorsModel()
@@ -79,6 +91,7 @@ constructor(
// The notification pipeline needs everything to run on the main thread, so keep
// this event on the main thread.
applicationScope.launch {
+ // TODO(b/364653005): Move accessibility focus to the HUN when chip is tapped.
notifChipsInteractor.onPromotedNotificationChipTapped(this@toActivityChipModel.key)
}
}
@@ -173,4 +186,16 @@ constructor(
}
}
}
+
+ private fun getContentDescription(appName: String): ContentDescription {
+ val ongoingDescription =
+ context.getString(R.string.ongoing_notification_extra_content_description)
+ return ContentDescription.Loaded(
+ context.getString(
+ R.string.accessibility_desc_notification_icon,
+ appName,
+ ongoingDescription,
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
index f5764d59e6ff..de9d4974c0c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt
@@ -26,6 +26,8 @@ import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.UiThread
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.ui.binder.ContentDescriptionViewBinder
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.res.R
import com.android.systemui.statusbar.StatusBarIconView
@@ -187,7 +189,13 @@ object OngoingActivityChipBinder {
}
is OngoingActivityChipModel.ChipIcon.StatusBarView -> {
StatusBarConnectedDisplays.assertInLegacyMode()
- setStatusBarIconView(defaultIconView, icon.impl, iconTint, backgroundView)
+ setStatusBarIconView(
+ defaultIconView,
+ icon.impl,
+ icon.contentDescription,
+ iconTint,
+ backgroundView,
+ )
}
is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon -> {
StatusBarConnectedDisplays.assertInNewMode()
@@ -196,7 +204,13 @@ object OngoingActivityChipBinder {
// This means that the notification key doesn't exist anymore.
return
}
- setStatusBarIconView(defaultIconView, iconView, iconTint, backgroundView)
+ setStatusBarIconView(
+ defaultIconView,
+ iconView,
+ icon.contentDescription,
+ iconTint,
+ backgroundView,
+ )
}
}
}
@@ -215,6 +229,7 @@ object OngoingActivityChipBinder {
private fun setStatusBarIconView(
defaultIconView: ImageView,
iconView: StatusBarIconView,
+ iconContentDescription: ContentDescription,
iconTint: Int,
backgroundView: ChipBackgroundContainer,
) {
@@ -224,9 +239,12 @@ object OngoingActivityChipBinder {
// 1. Set up the right visual params.
with(iconView) {
id = CUSTOM_ICON_VIEW_ID
- // TODO(b/354930838): For RON chips, use the app name for the content description.
- contentDescription =
- context.resources.getString(R.string.ongoing_call_content_description)
+ if (StatusBarNotifChips.isEnabled) {
+ ContentDescriptionViewBinder.bind(iconContentDescription, this)
+ } else {
+ contentDescription =
+ context.resources.getString(R.string.ongoing_call_content_description)
+ }
tintView(iconTint)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 647f3bd469f1..816f291b9273 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -37,11 +37,14 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import com.android.compose.animation.Expandable
import com.android.systemui.animation.Expandable
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.common.ui.compose.load
import com.android.systemui.res.R
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
@@ -82,11 +85,25 @@ private fun ChipBody(
val isClickable = onClick != {}
val hasEmbeddedIcon = model.icon is OngoingActivityChipModel.ChipIcon.StatusBarView
+ val contentDescription =
+ when (val icon = model.icon) {
+ is OngoingActivityChipModel.ChipIcon.StatusBarView -> icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon ->
+ icon.contentDescription.load()
+ is OngoingActivityChipModel.ChipIcon.SingleColorIcon -> null
+ null -> null
+ }
+
// Use a Box with `fillMaxHeight` to create a larger click surface for the chip. The visible
// height of the chip is determined by the height of the background of the Row below.
Box(
contentAlignment = Alignment.Center,
- modifier = modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick),
+ modifier =
+ modifier.fillMaxHeight().clickable(enabled = isClickable, onClick = onClick).semantics {
+ if (contentDescription != null) {
+ this.contentDescription = contentDescription
+ }
+ },
) {
Row(
horizontalArrangement = Arrangement.Center,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
index e0c764570132..d44646cb6144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.chips.ui.model
import android.view.View
import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -140,7 +141,10 @@ sealed class OngoingActivityChipModel {
* The icon is a custom icon, which is set on [impl]. The icon was likely created by an
* external app.
*/
- data class StatusBarView(val impl: StatusBarIconView) : ChipIcon {
+ data class StatusBarView(
+ val impl: StatusBarIconView,
+ val contentDescription: ContentDescription,
+ ) : ChipIcon {
init {
StatusBarConnectedDisplays.assertInLegacyMode()
}
@@ -150,7 +154,10 @@ sealed class OngoingActivityChipModel {
* The icon is a custom icon, which is set on a notification, and can be looked up using the
* provided [notificationKey]. The icon was likely created by an external app.
*/
- data class StatusBarNotificationIcon(val notificationKey: String) : ChipIcon {
+ data class StatusBarNotificationIcon(
+ val notificationKey: String,
+ val contentDescription: ContentDescription,
+ ) : ChipIcon {
init {
StatusBarConnectedDisplays.assertInNewMode()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
index b057fb0433fe..eeb7a4066eca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.core
import android.view.Display
+import android.view.IWindowManager
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
@@ -54,6 +55,7 @@ constructor(
private val autoHideControllerStore: AutoHideControllerStore,
private val privacyDotWindowControllerStore: PrivacyDotWindowControllerStore,
private val lightBarControllerStore: LightBarControllerStore,
+ private val windowManager: IWindowManager,
) : CoreStartable {
init {
@@ -68,7 +70,13 @@ constructor(
}
.onStart { emit(displayRepository.displays.value) }
.collect { newDisplays ->
- newDisplays.forEach { createAndStartComponentsForDisplay(it) }
+ newDisplays.forEach {
+ // TODO(b/393191204): Split navbar, status bar, etc. functionality
+ // from WindowManager#shouldShowSystemDecors.
+ if (windowManager.shouldShowSystemDecors(it.displayId)) {
+ createAndStartComponentsForDisplay(it)
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
index 7358c513eaff..9d5d87f6db7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/layout/StatusBarContentInsetsProvider.kt
@@ -185,7 +185,7 @@ constructor(
override fun start() {
configurationController.addCallback(this)
- dumpManager.registerDumpable(dumpableName, this)
+ dumpManager.registerNormalDumpable(dumpableName, this)
commandRegistry.registerCommand(commandName) {
StatusBarInsetsCommand(
object : StatusBarInsetsCommand.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index fd5973e0ab3b..bde3c4d8c632 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -21,10 +21,12 @@ import android.app.Notification.CallStyle.CALL_TYPE_SCREENING
import android.app.Notification.CallStyle.CALL_TYPE_UNKNOWN
import android.app.Notification.EXTRA_CALL_TYPE
import android.app.PendingIntent
+import android.content.Context
import android.graphics.drawable.Icon
import android.service.notification.StatusBarNotification
import android.util.ArrayMap
import com.android.app.tracing.traceSection
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -50,6 +52,7 @@ class RenderNotificationListInteractor
constructor(
private val repository: ActiveNotificationListRepository,
private val sectionStyleProvider: SectionStyleProvider,
+ @Main private val context: Context,
) {
/**
* Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +60,7 @@ constructor(
fun setRenderedList(entries: List<ListEntry>) {
traceSection("RenderNotificationListInteractor.setRenderedList") {
repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ buildActiveNotificationsStore(existingModels, sectionStyleProvider, context) {
entries.forEach(::addListEntry)
setRankingsMap(entries)
}
@@ -69,13 +72,17 @@ constructor(
private fun buildActiveNotificationsStore(
existingModels: ActiveNotificationsStore,
sectionStyleProvider: SectionStyleProvider,
+ context: Context,
block: ActiveNotificationsStoreBuilder.() -> Unit,
): ActiveNotificationsStore =
- ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+ ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider, context)
+ .apply(block)
+ .build()
private class ActiveNotificationsStoreBuilder(
private val existingModels: ActiveNotificationsStore,
private val sectionStyleProvider: SectionStyleProvider,
+ private val context: Context,
) {
private val builder = ActiveNotificationsStore.Builder()
@@ -154,6 +161,7 @@ private class ActiveNotificationsStoreBuilder(
statusBarChipIconView = icons.statusBarChipIcon,
uid = sbn.uid,
packageName = sbn.packageName,
+ appName = sbn.notification.loadHeaderAppName(context),
contentIntent = sbn.notification.contentIntent,
instanceId = sbn.instanceId?.id,
isGroupSummary = sbn.notification.isGroupSummary,
@@ -180,6 +188,7 @@ private fun ActiveNotificationsStore.createOrReuse(
statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
+ appName: String,
contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
@@ -206,6 +215,7 @@ private fun ActiveNotificationsStore.createOrReuse(
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
@@ -230,6 +240,7 @@ private fun ActiveNotificationsStore.createOrReuse(
instanceId = instanceId,
isGroupSummary = isGroupSummary,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
bucket = bucket,
callType = callType,
@@ -253,6 +264,7 @@ private fun ActiveNotificationModel.isCurrent(
statusBarChipIconView: StatusBarIconView?,
uid: Int,
packageName: String,
+ appName: String,
contentIntent: PendingIntent?,
instanceId: Int?,
isGroupSummary: Boolean,
@@ -278,6 +290,7 @@ private fun ActiveNotificationModel.isCurrent(
instanceId != this.instanceId -> false
isGroupSummary != this.isGroupSummary -> false
packageName != this.packageName -> false
+ appName != this.appName -> false
contentIntent != this.contentIntent -> false
bucket != this.bucket -> false
callType != this.callType -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 33c71d4a9c5a..c3ef5f755b01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -351,7 +351,7 @@ private class AODPromotedNotificationViewUpdater(root: View) {
setTextViewColor(view, color)
if (text != null && text.isNotEmpty()) {
- view?.text = text
+ view?.text = text.toSkeleton()
view?.visibility = VISIBLE
} else {
view?.text = ""
@@ -364,6 +364,10 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
+private fun CharSequence.toSkeleton(): CharSequence {
+ return this.toString()
+}
+
private enum class AodPromotedNotificationColor(colorUInt: UInt) {
Background(0x00000000u),
PrimaryText(0xFFFFFFFFu),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
index 4ccdc65ba91a..468934791525 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationLogger.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.promoted
import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.keyguard.ui.view.layout.sections.AodPromotedNotificationSection
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel.ERROR
import com.android.systemui.log.core.LogLevel.INFO
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
import javax.inject.Inject
+@OptIn(ExperimentalStdlibApi::class)
class PromotedNotificationLogger
@Inject
constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
@@ -92,20 +94,44 @@ constructor(@PromotedNotificationLog private val buffer: LogBuffer) {
buffer.log(AOD_VIEW_BINDER_TAG, INFO, "binder unbound notification")
}
- fun logSectionAddedViews() {
- buffer.log(AOD_SECTION_TAG, INFO, "section added views")
+ fun logSectionCreated(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} created",
+ )
}
- fun logSectionBoundData() {
- buffer.log(AOD_SECTION_TAG, INFO, "section bound data")
+ fun logSectionAddedViews(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} added views",
+ )
}
- fun logSectionAppliedConstraints() {
- buffer.log(AOD_SECTION_TAG, INFO, "section applied constraints")
+ fun logSectionBoundData(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} bound data",
+ )
}
- fun logSectionRemovedViews() {
- buffer.log(AOD_SECTION_TAG, INFO, "section removed views")
+ fun logSectionAppliedConstraints(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} applied constraints",
+ )
+ }
+
+ fun logSectionRemovedViews(section: AodPromotedNotificationSection) {
+ buffer.log(
+ AOD_SECTION_TAG,
+ INFO,
+ "section ${System.identityHashCode(section).toHexString()} removed views",
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index ab8be306ab5e..f00c3ae20e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -68,6 +68,8 @@ data class ActiveNotificationModel(
val uid: Int,
/** The notifying app's packageName. */
val packageName: String,
+ /** The notifying app's display name. */
+ val appName: String,
/** The intent to execute if UI related to this notification is clicked. */
val contentIntent: PendingIntent?,
/** A small per-notification ID, used for statsd logging. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index f7401440cfcb..ece1803e14c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -28,6 +28,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.ViewStateAccessor
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
@@ -143,7 +144,7 @@ constructor(
}
if (!SceneContainerFlag.isEnabled) {
- if (Flags.magicPortraitWallpapers()) {
+ if (extendedWallpaperEffects()) {
launch {
combine(
viewModel.getNotificationStackAbsoluteBottom(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 09531c3b18bd..01f2e9b8371d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -230,7 +230,11 @@ public final class DozeServiceHost implements DozeHost {
mDozingRequested = true;
updateDozing();
mDozeLog.traceDozing(mStatusBarStateController.isDozing());
- mCentralSurfaces.updateIsKeyguard();
+ // This is initialized in a CoreStartable, but binder calls from DreamManagerService can
+ // arrive earlier
+ if (mCentralSurfaces != null) {
+ mCentralSurfaces.updateIsKeyguard();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index a29934fa3a16..949cb0a718d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -160,6 +160,7 @@ constructor(
notificationIconView = currentInfo.notificationIconView,
intent = currentInfo.intent,
notificationKey = currentInfo.key,
+ appName = currentInfo.appName,
promotedContent = currentInfo.promotedContent,
)
} else {
@@ -217,6 +218,7 @@ constructor(
notifModel.statusBarChipIconView,
notifModel.contentIntent,
notifModel.uid,
+ notifModel.appName,
notifModel.promotedContent,
isOngoing = true,
statusBarSwipedAway = callNotificationInfo?.statusBarSwipedAway ?: false,
@@ -337,6 +339,7 @@ constructor(
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
val uid: Int,
+ val appName: String,
/**
* If the call notification also meets promoted notification criteria, this field is filled
* in with the content related to promotion. Otherwise null.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
index ba7628fb3c07..2fd7d82043a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/domain/interactor/OngoingCallInteractor.kt
@@ -163,6 +163,7 @@ constructor(
notificationIconView = model.statusBarChipIconView,
intent = model.contentIntent,
notificationKey = model.key,
+ appName = model.appName,
promotedContent = model.promotedContent,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
index 7d00e9d58e5b..6507b727eb48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModel.kt
@@ -42,6 +42,7 @@ sealed interface OngoingCallModel {
* @property notificationIconView the [android.app.Notification.getSmallIcon] that's set on the
* call notification. We may use this icon in the chip instead of the default phone icon.
* @property intent the intent associated with the call notification.
+ * @property appName the user-readable name of the app that posted the call notification.
* @property promotedContent if the call notification also meets promoted notification criteria,
* this field is filled in with the content related to promotion. Otherwise null.
*/
@@ -50,6 +51,7 @@ sealed interface OngoingCallModel {
val notificationIconView: StatusBarIconView?,
val intent: PendingIntent?,
val notificationKey: String,
+ val appName: String,
val promotedContent: PromotedNotificationContentModel?,
) : OngoingCallModel
}
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
index 9794c619041e..79a9630e6887 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/data/repository/WallpaperRepository.kt
@@ -27,12 +27,13 @@ import android.view.View
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
-import com.android.systemui.Flags
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.res.R as SysUIR
import com.android.systemui.shared.Flags.ambientAod
+import com.android.systemui.shared.Flags.extendedWallpaperEffects
import com.android.systemui.user.data.model.SelectedUserModel
import com.android.systemui.user.data.model.SelectionStatus
import com.android.systemui.user.data.repository.UserRepository
@@ -66,7 +67,7 @@ interface WallpaperRepository {
/** Set rootView to get its windowToken afterwards */
var rootView: View?
- /** when we use magic portrait wallpapers, we should always get its bounds from keyguard */
+ /** some wallpapers require bounds to be sent from keyguard */
val shouldSendFocalArea: StateFlow<Boolean>
}
@@ -80,7 +81,7 @@ constructor(
userRepository: UserRepository,
keyguardRepository: KeyguardRepository,
private val wallpaperManager: WallpaperManager,
- context: Context,
+ private val context: Context,
) : WallpaperRepository {
private val wallpaperChanged: Flow<Unit> =
broadcastDispatcher
@@ -125,8 +126,8 @@ constructor(
override val shouldSendFocalArea =
wallpaperInfo
.map {
- val shouldSendNotificationLayout =
- it?.component?.className == MAGIC_PORTRAIT_CLASSNAME
+ val focalAreaTarget = context.resources.getString(SysUIR.string.focal_area_target)
+ val shouldSendNotificationLayout = it?.component?.className == focalAreaTarget
if (shouldSendNotificationLayout) {
sendLockscreenLayoutJob =
scope.launch {
@@ -167,9 +168,8 @@ constructor(
}
.stateIn(
scope,
- // Always be listening for wallpaper changes when magic portrait flag is on
- if (Flags.magicPortraitWallpapers()) SharingStarted.Eagerly else WhileSubscribed(),
- initialValue = Flags.magicPortraitWallpapers(),
+ if (extendedWallpaperEffects()) SharingStarted.Eagerly else WhileSubscribed(),
+ initialValue = extendedWallpaperEffects(),
)
private suspend fun getWallpaper(selectedUser: SelectedUserModel): WallpaperInfo? {
@@ -177,9 +177,4 @@ constructor(
wallpaperManager.getWallpaperInfoForUser(selectedUser.userInfo.id)
}
}
-
- companion object {
- const val MAGIC_PORTRAIT_CLASSNAME =
- "com.google.android.apps.magicportrait.service.MagicPortraitWallpaperService"
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index bac2c47f51c7..f822ee97807e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -95,7 +95,6 @@ import org.mockito.kotlin.clearInvocations
@RunWith(AndroidJUnit4::class)
@SmallTest
-@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
class ClockEventControllerTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
index 4e0ebae6a902..c6ede0c8c602 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplForDeviceTest.kt
@@ -36,7 +36,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.FieldSetter
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -55,7 +54,6 @@ import org.mockito.junit.MockitoRule
* can't mock the AccessibilityShortcutInfo for test. MultiValentTest doesn't compile when using
* newly introduced methods and constants.
*/
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AccessibilityQsShortcutsRepositoryImplForDeviceTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 4baca713e19f..5249620dbdd0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -63,7 +63,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -89,7 +88,6 @@ private const val DISPLAY_HEIGHT = 1920
private const val SENSOR_WIDTH = 30
private const val SENSOR_HEIGHT = 60
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
index 655b2cc2dece..f3aaa3d3c7aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingButtonViewModelTest.kt
@@ -32,7 +32,6 @@ import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
@@ -43,7 +42,6 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
index e61acc4e1d0b..9ae57153f3ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/AudioSharingDeviceItemActionInteractorTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.plugins.activityStarter
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -56,7 +55,6 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
class AudioSharingDeviceItemActionInteractorTest : SysuiTestCase() {
@get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index a56c2cb25542..242d31a26b13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -45,7 +45,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -67,7 +66,6 @@ import org.mockito.junit.MockitoRule
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
@EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
class BluetoothTileDialogViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
index 8d9fa6ad6e08..e50035d0067e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderContentProviderTest.kt
@@ -41,7 +41,6 @@ const val AUTHORITY = "exception.provider.authority"
val TEST_URI = Uri.Builder().scheme("content").authority(AUTHORITY).path("path").build()
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class ImageLoaderContentProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
index 76c3349c25ba..de70bca7e96c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/graphics/ImageLoaderTest.kt
@@ -29,7 +29,6 @@ import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class ImageLoaderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index a3c3d2cdbb43..32b8bb49de96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -70,7 +70,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -84,7 +83,6 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 38acd23d282c..0c9213c3a722 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -206,6 +206,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private @Mock ShadeInteractor mShadeInteractor;
private @Mock ShadeWindowLogger mShadeWindowLogger;
private @Mock SelectedUserInteractor mSelectedUserInteractor;
+ private @Mock UserTracker.Callback mUserTrackerCallback;
private @Mock KeyguardInteractor mKeyguardInteractor;
private @Mock KeyguardTransitionBootInteractor mKeyguardTransitionBootInteractor;
private @Captor ArgumentCaptor<KeyguardStateController.Callback>
@@ -280,7 +281,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
() -> mShadeInteractor,
mShadeWindowLogger,
() -> mSelectedUserInteractor,
- mUserTracker,
+ mock(UserTracker.class),
mKosmos.getNotificationShadeWindowModel(),
mSecureSettings,
mKosmos::getCommunalInteractor,
@@ -318,7 +319,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
} catch (Exception e) {
// Just so we don't have to add the exception signature to every test.
- fail();
+ fail(e.getMessage());
}
}
@@ -330,18 +331,156 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
/* First test the default behavior: handleUserSwitching() is not invoked */
when(mUserTracker.isUserSwitching()).thenReturn(false);
- mViewMediator.mUpdateCallback = mock(KeyguardUpdateMonitorCallback.class);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mViewMediator.mUpdateCallback, never()).onUserSwitching(userId);
+ verify(mUserTrackerCallback, never()).onUserChanging(eq(userId), eq(mContext),
+ any(Runnable.class));
/* Next test user switching is already in progress when started */
when(mUserTracker.isUserSwitching()).thenReturn(true);
mViewMediator.onSystemReady();
TestableLooper.get(this).processAllMessages();
- verify(mViewMediator.mUpdateCallback).onUserSwitching(userId);
+ verify(mUserTrackerCallback).onUserChanging(eq(userId), eq(mContext),
+ any(Runnable.class));
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testGoingAwayFollowedByBeforeUserSwitchDoesNotHideKeyguard() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */false);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // Request keyguard going away
+ when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mViewMediator.mKeyguardGoingAwayRunnable.run();
+
+ // After the request, begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+ Runnable result = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, result);
+ processAllMessagesAndBgExecutorMessages();
+ verify(result).run();
+
+ // After that request has begun, have WM tell us to exit keyguard
+ RemoteAnimationTarget[] apps = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ RemoteAnimationTarget[] wallpapers = new RemoteAnimationTarget[]{
+ mock(RemoteAnimationTarget.class)
+ };
+ IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
+ mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
+ null, callback);
+ processAllMessagesAndBgExecutorMessages();
+
+ // The call to exit should be rejected, and keyguard should still be visible
+ verify(mKeyguardUnlockAnimationController, never()).notifyStartSurfaceBehindRemoteAnimation(
+ any(), any(), any(), anyLong(), anyBoolean());
+ try {
+ assertATMSLockScreenShowing(true);
+ } catch (Exception e) {
+ fail(e.getMessage());
+ }
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToSecureUserShowsBouncer() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */true);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // After the request, begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ // Dismiss should not be called while user switch is in progress
+ Runnable onSwitchResult = mock(Runnable.class);
+ mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(onSwitchResult).run();
+ verify(mStatusBarKeyguardViewManager, never()).dismissAndCollapse();
+
+ // The attempt to dismiss only comes on user switch complete, which will trigger a call to
+ // show the bouncer in StatusBarKeyguardViewManager
+ mViewMediator.handleUserSwitchComplete(nextUserId);
+ TestableLooper.get(this).moveTimeForward(600);
+ processAllMessagesAndBgExecutorMessages();
+
+ verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToInsecureUserDismissesKeyguard() {
+ int userId = 1099;
+ when(mUserTracker.getUserId()).thenReturn(userId);
+
+ // Setup keyguard
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(true, "");
+
+ // After the request, begin a switch to an insecure user
+ int nextUserId = 500;
+ when(mLockPatternUtils.isSecure(nextUserId)).thenReturn(false);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ // The call to dismiss comes during the user switch
+ Runnable onSwitchResult = mock(Runnable.class);
+ mViewMediator.handleUserSwitching(nextUserId, onSwitchResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(onSwitchResult).run();
+
+ verify(mStatusBarKeyguardViewManager).dismissAndCollapse();
+ }
+
+ @Test
+ @TestableLooper.RunWithLooper(setAsMainLooper = true)
+ public void testUserSwitchToSecureUserWhileKeyguardNotVisibleShowsKeyguard() {
+ setCurrentUser(/* userId= */1099, /* isSecure= */true);
+
+ // Setup keyguard as not visible
+ mViewMediator.onSystemReady();
+ processAllMessagesAndBgExecutorMessages();
+ mViewMediator.setShowingLocked(false, "");
+ processAllMessagesAndBgExecutorMessages();
+
+ // Begin a switch to a new secure user
+ int nextUserId = 500;
+ setCurrentUser(nextUserId, /* isSecure= */true);
+
+ Runnable beforeResult = mock(Runnable.class);
+ mViewMediator.handleBeforeUserSwitching(nextUserId, beforeResult);
+ processAllMessagesAndBgExecutorMessages();
+ verify(beforeResult).run();
+
+ try {
+ assertATMSLockScreenShowing(true);
+ } catch (Exception e) {
+ fail();
+ }
+ assertTrue(mViewMediator.isShowingAndNotOccluded());
}
@Test
@@ -1105,7 +1244,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
processAllMessagesAndBgExecutorMessages();
verify(mStatusBarKeyguardViewManager, never()).reset(anyBoolean());
- assertATMSAndKeyguardViewMediatorStatesMatch();
+
}
@Test
@@ -1149,6 +1288,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
IRemoteAnimationFinishedCallback callback = mock(IRemoteAnimationFinishedCallback.class);
when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true);
+ mViewMediator.mKeyguardGoingAwayRunnable.run();
mViewMediator.startKeyguardExitAnimation(TRANSIT_OLD_KEYGUARD_GOING_AWAY, apps, wallpapers,
null, callback);
processAllMessagesAndBgExecutorMessages();
@@ -1203,13 +1343,6 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
// The captor will have the most recent setLockScreenShown call's value.
assertEquals(showing, showingCaptor.getValue());
-
- // We're now just after the last setLockScreenShown call. If we expect the lockscreen to be
- // showing, ensure that we didn't subsequently ask for it to go away.
- if (showing) {
- orderedSetLockScreenShownCalls.verify(mActivityTaskManagerService, never())
- .keyguardGoingAway(anyInt());
- }
}
/**
@@ -1370,6 +1503,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mKeyguardInteractor,
mKeyguardTransitionBootInteractor,
mock(WindowManagerOcclusionManager.class));
+ mViewMediator.mUserChangedCallback = mUserTrackerCallback;
mViewMediator.start();
mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null);
@@ -1383,4 +1517,10 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
private void captureKeyguardUpdateMonitorCallback() {
verify(mUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallbackCaptor.capture());
}
+
+ private void setCurrentUser(int userId, boolean isSecure) {
+ when(mUserTracker.getUserId()).thenReturn(userId);
+ when(mSelectedUserInteractor.getSelectedUserId()).thenReturn(userId);
+ when(mLockPatternUtils.isSecure(userId)).thenReturn(isSecure);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index ab691c630f97..a17125388ed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -59,7 +59,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -79,7 +78,6 @@ import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@DisableSceneContainer
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index caf08efc4b32..2648c9c6496e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -60,7 +60,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.settings.FakeSettings
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
@@ -79,7 +78,6 @@ import platform.test.runner.parameterized.Parameter
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@FlakyTest(
bugId = 292574995,
detail = "on certain architectures all permutations with startActivity=true is causing failures",
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 418972055324..df24bff43c08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -38,7 +38,6 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import org.junit.Before
@@ -49,7 +48,6 @@ import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
-@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
@SmallTest
class DefaultDeviceEntrySectionTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 051aba3d593f..26c13f2a2519 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -77,7 +77,6 @@ import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth
import kotlin.math.min
import kotlin.test.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
@@ -90,7 +89,6 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
index e3aeaa85873d..fe5acb5668ac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/RepeatWhenAttachedTest.kt
@@ -28,7 +28,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.DisposableHandle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.launch
@@ -52,7 +51,6 @@ import org.mockito.junit.MockitoJUnit
import org.mockito.kotlin.KArgumentCaptor
import org.mockito.kotlin.argumentCaptor
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class RepeatWhenAttachedTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
index 676d8fa06d82..122af0639030 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt
@@ -73,7 +73,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -117,7 +116,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index 811d2e2b2b06..b731c4f18c7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -49,7 +49,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -81,7 +80,6 @@ private const val SMARTSPACE_KEY = "SMARTSPACE_KEY"
private const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG"
private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
-@ExperimentalCoroutinesApi
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 496b31990b9d..f9e8cbfc491c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -80,7 +80,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runCurrent
@@ -123,7 +122,6 @@ private const val SESSION_EMPTY_TITLE = ""
private const val USER_ID = 0
private val DISMISS_INTENT = Intent().apply { action = "dismiss" }
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index 6c8a46f19f2b..a2bd5ec28f08 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -79,7 +79,6 @@ import java.util.Locale
import javax.inject.Provider
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertTrue
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -112,7 +111,6 @@ private val SMARTSPACE_KEY = "smartspace"
private const val PAUSED_LOCAL = "paused local"
private const val PLAYING_LOCAL = "playing local"
-@ExperimentalCoroutinesApi
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(ParameterizedAndroidJunit4::class)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 072caa74df20..6ca4ae2d2259 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -61,7 +61,6 @@ import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -86,7 +85,6 @@ import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.atLeastOnce
import org.mockito.kotlin.lastValue
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@@ -458,7 +456,6 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isTrue()
}
- @OptIn(ExperimentalCoroutinesApi::class)
@Test
fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() =
testScope.runTest {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 7849ea5ab7ed..042eb871e3c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -70,7 +70,6 @@ import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlin.test.assertNotNull
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
@@ -90,7 +89,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.whenever
/** atest SystemUITests:NoteTaskControllerTest */
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskControllerTest : SysuiTestCase() {
@@ -179,7 +177,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
.apply { infoReference.set(expectedInfo) }
.onBubbleExpandChanged(
isExpanding = true,
- key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
+ key = Bubble.getNoteBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
verify(eventLogger).logNoteTaskOpened(expectedInfo)
@@ -194,7 +192,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
.apply { infoReference.set(expectedInfo) }
.onBubbleExpandChanged(
isExpanding = false,
- key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
+ key = Bubble.getNoteBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
verify(eventLogger).logNoteTaskClosed(expectedInfo)
@@ -209,7 +207,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
.apply { infoReference.set(expectedInfo) }
.onBubbleExpandChanged(
isExpanding = true,
- key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
+ key = Bubble.getNoteBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
@@ -223,14 +221,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
.apply { infoReference.set(expectedInfo) }
.onBubbleExpandChanged(
isExpanding = false,
- key = Bubble.getAppBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
+ key = Bubble.getNoteBubbleKeyForApp(expectedInfo.packageName, expectedInfo.user),
)
verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
}
@Test
- fun onBubbleExpandChanged_notKeyAppBubble_shouldDoNothing() {
+ fun onBubbleExpandChanged_notKeyNoteBubble_shouldDoNothing() {
createNoteTaskController().onBubbleExpandChanged(isExpanding = true, key = "any other key")
verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
@@ -241,7 +239,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
createNoteTaskController(isEnabled = false)
.onBubbleExpandChanged(
isExpanding = true,
- key = Bubble.getAppBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
+ key = Bubble.getNoteBubbleKeyForApp(NOTE_TASK_INFO.packageName, NOTE_TASK_INFO.user),
)
verifyNoMoreInteractions(bubbles, keyguardManager, userManager, eventLogger)
@@ -740,7 +738,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
val intentCaptor = argumentCaptor<Intent>()
val iconCaptor = argumentCaptor<Icon>()
verify(bubbles)
- .showOrHideAppBubble(capture(intentCaptor), eq(userHandle), capture(iconCaptor))
+ .showOrHideNoteBubble(capture(intentCaptor), eq(userHandle), capture(iconCaptor))
assertThat(intentCaptor.value).run {
hasAction(ACTION_CREATE_NOTE)
hasPackage(NOTE_TASK_PACKAGE_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 266cb51cc855..8a4f1ad13b78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -47,7 +47,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.android.wm.shell.bubbles.Bubbles
import com.google.common.truth.Truth.assertThat
import java.util.Optional
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -60,7 +59,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations.initMocks
/** atest SystemUITests:NoteTaskInitializerTest */
-@OptIn(ExperimentalCoroutinesApi::class, InternalNoteTaskApi::class)
+@OptIn(InternalNoteTaskApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
internal class NoteTaskInitializerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index b7fb759eb138..8d7de7e04a03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.notetask.quickaffordance
import android.app.role.RoleManager
@@ -48,7 +46,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.After
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
index c8faa81adffa..cf54df8565d3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayCoreStartableTest.kt
@@ -45,7 +45,6 @@ import org.mockito.kotlin.whenever
/** atest SystemUITests:com.android.systemui.reardisplay.RearDisplayCoreStartableTest */
@SmallTest
-@kotlinx.coroutines.ExperimentalCoroutinesApi
class RearDisplayCoreStartableTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
index f695c13a9e62..1474defa1662 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt
@@ -38,7 +38,6 @@ import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.TruthJUnit.assume
import java.util.concurrent.Executor
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -58,7 +57,6 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(Parameterized::class)
class UserTrackerImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 6724f82dfd99..732561e0979b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -69,7 +69,6 @@ import com.android.systemui.statusbar.lockscreen.lockscreenSmartspaceController
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -87,7 +86,6 @@ import org.mockito.kotlin.spy
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-@ExperimentalCoroutinesApi
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 49d6909c1f93..856ece7e7ff3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -89,7 +89,6 @@ import com.android.systemui.window.ui.viewmodel.WindowRootViewModel
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.test.TestScope
@@ -116,7 +115,6 @@ import org.mockito.kotlin.clearInvocations
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
@RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index cfc00a918f61..b7040ee2a11e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -35,7 +35,6 @@ import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.After
@@ -69,7 +68,6 @@ private fun <T> anyObject(): T {
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
-@OptIn(ExperimentalCoroutinesApi::class)
class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 98487f7ac059..f8154ddb2a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -3,7 +3,6 @@ package com.android.systemui.statusbar
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
@@ -30,7 +29,6 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
private val configurationController = FakeConfigurationController()
- @OptIn(ExperimentalCoroutinesApi::class)
@Mock private lateinit var scrimController: ScrimController
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private var qS: QS? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 77ca51c5efcb..3c4aeccec2eb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -41,7 +41,6 @@ import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import com.android.systemui.util.time.FakeSystemClock
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
@@ -64,7 +63,6 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
index c4ef4f978ff8..a16f2f6161ff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
package com.android.systemui.statusbar.notification.icon
import android.app.ActivityManager
@@ -43,7 +41,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
index 8645a40319f4..7cc1ac1d3806 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerWithScenesTest.kt
@@ -75,7 +75,6 @@ import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.wmshell.BubblesManager
import java.util.Optional
import kotlin.test.assertEquals
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runCurrent
import org.junit.Assert
@@ -98,7 +97,6 @@ import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
/** Tests for [NotificationGutsManager] with the scene container enabled. */
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
index 8beed01ffbe4..f4c0e367871f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/airplane/ui/viewmodel/AirplaneModeViewModelImplTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
@@ -39,7 +38,6 @@ import org.junit.runner.RunWith
import org.mockito.MockitoAnnotations
@SmallTest
-@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@RunWith(AndroidJUnit4::class)
class AirplaneModeViewModelImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
index 4a2f6f281566..df74404aaaf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SystemUiCarrierConfigTest.kt
@@ -25,12 +25,10 @@ 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 kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class SystemUiCarrierConfigTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
index 7d101be4e748..d074fc256133 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/CarrierConfigRepositoryImplTest.kt
@@ -29,7 +29,6 @@ import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest.Companion.createTestConfig
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.test.TestScope
@@ -45,7 +44,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.MockitoSession
import org.mockito.quality.Strictness
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class CarrierConfigRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 36f5236c3936..d05590521fab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -50,7 +50,6 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
@@ -70,7 +69,6 @@ import org.mockito.MockitoAnnotations
* switches over when the value of `demoMode` changes
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileRepositorySwitcherTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index fd23655ffc1c..02ad90cb4269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -50,7 +50,6 @@ import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -69,7 +68,6 @@ import org.mockito.Mockito.verify
* properly switches over when the value of `isCarrierMerged` changes.
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class FullMobileConnectionRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3a25ecb27404..df5c6e916931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -98,7 +98,6 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -115,7 +114,6 @@ import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class MobileConnectionRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
index 0d82c79fea79..ec260fcc7a65 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionTelephonySmokeTests.kt
@@ -49,7 +49,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityMod
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -92,7 +91,6 @@ import org.mockito.MockitoAnnotations
* 5. Assert that B has the state sent in step #2
*/
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class MobileConnectionTelephonySmokeTests : SysuiTestCase() {
private lateinit var underTest: MobileConnectionRepositoryImpl
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6c60f55a3904..d1d6e27332b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -79,7 +79,6 @@ import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
import java.util.UUID
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -101,7 +100,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
// This is required because our [SubscriptionManager.OnSubscriptionsChangedListener] uses a looper
// to run the callback and this makes the looper place nicely with TestScope etc.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
index 84846a16f39a..ce99e595504d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileViewTest.kt
@@ -44,7 +44,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
@@ -57,7 +56,6 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
-@OptIn(ExperimentalCoroutinesApi::class)
class ModernStatusBarMobileViewTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 599729d953d4..6a8b783acf73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -53,7 +53,6 @@ import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
import kotlin.test.Test
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -71,7 +70,6 @@ import org.mockito.MockitoAnnotations
import org.mockito.kotlin.doThrow
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
index 88f262bec123..8a0159ce174e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepositoryImplTest.kt
@@ -47,7 +47,6 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.Connectivi
import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -60,7 +59,6 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class ConnectivityRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 5d88f72b805b..097f3929db42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -26,8 +26,6 @@ import static android.service.notification.NotificationListenerService.REASON_AP
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
-import static androidx.test.ext.truth.content.IntentSubject.assertThat;
-
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
import static com.android.wm.shell.Flags.FLAG_ENABLE_BUBBLE_BAR;
@@ -175,6 +173,7 @@ import com.android.wm.shell.bubbles.BubbleEducationController;
import com.android.wm.shell.bubbles.BubbleEntry;
import com.android.wm.shell.bubbles.BubbleLogger;
import com.android.wm.shell.bubbles.BubbleOverflow;
+import com.android.wm.shell.bubbles.BubbleResizabilityChecker;
import com.android.wm.shell.bubbles.BubbleStackView;
import com.android.wm.shell.bubbles.BubbleTaskView;
import com.android.wm.shell.bubbles.BubbleViewInfoTask;
@@ -182,7 +181,6 @@ import com.android.wm.shell.bubbles.BubbleViewProvider;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.bubbles.StackEducationView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
-import com.android.wm.shell.bubbles.properties.BubbleProperties;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -297,7 +295,7 @@ public class BubblesTest extends SysuiTestCase {
private BubbleEntry mBubbleEntryUser11;
private BubbleEntry mBubbleEntry2User11;
- private Intent mAppBubbleIntent;
+ private Intent mNotesBubbleIntent;
@Mock
private ShellInit mShellInit;
@@ -354,7 +352,7 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private NotifPipelineFlags mNotifPipelineFlags;
@Mock
- private Icon mAppBubbleIcon;
+ private Icon mNotesBubbleIcon;
@Mock
private Display mDefaultDisplay;
@Mock
@@ -380,8 +378,6 @@ public class BubblesTest extends SysuiTestCase {
private UserHandle mUser0;
- private FakeBubbleProperties mBubbleProperties;
-
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
@@ -457,8 +453,8 @@ public class BubblesTest extends SysuiTestCase {
mNotificationShadeWindowController.fetchWindowRootView();
mNotificationShadeWindowController.attach();
- mAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
- mAppBubbleIntent.setPackage(mContext.getPackageName());
+ mNotesBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
+ mNotesBubbleIntent.setPackage(mContext.getPackageName());
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
@@ -522,7 +518,6 @@ public class BubblesTest extends SysuiTestCase {
mTaskViewRepository = new TaskViewRepository();
mTaskViewTransitions = new TaskViewTransitions(mTransitions, mTaskViewRepository,
mShellTaskOrganizer, mSyncQueue);
- mBubbleProperties = new FakeBubbleProperties();
mBubbleController = new TestableBubbleController(
mContext,
mShellInit,
@@ -551,7 +546,7 @@ public class BubblesTest extends SysuiTestCase {
mTransitions,
mock(SyncTransactionQueue.class),
mock(IWindowManager.class),
- mBubbleProperties);
+ new BubbleResizabilityChecker());
mBubbleController.setExpandListener(mBubbleExpandListener);
spyOn(mBubbleController);
@@ -1478,8 +1473,8 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void testShowManageMenuChangesSysuiState_appBubble() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ public void testShowManageMenuChangesSysuiState_notesBubble() {
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
assertTrue(mBubbleController.hasBubbles());
// Expand the stack
@@ -1979,79 +1974,80 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void testShowOrHideAppBubble_addsAndExpand() {
+ public void testShowOrHideNotesBubble_addsAndExpand() {
assertThat(mBubbleController.isStackExpanded()).isFalse();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
verify(mBubbleController).inflateAndAdd(any(Bubble.class), /* suppressFlyout= */ eq(true),
/* showInShade= */ eq(false));
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(
- Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0));
+ Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), mUser0));
assertThat(mBubbleController.isStackExpanded()).isTrue();
}
@Test
- public void testShowOrHideAppBubble_expandIfCollapsed() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ public void testShowOrHideNotesBubble_expandIfCollapsed() {
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.collapseStack();
assertThat(mBubbleController.isStackExpanded()).isFalse();
// Calling this while collapsed will expand the app bubble
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(
- Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0));
+ Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), mUser0));
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
}
@Test
- public void testShowOrHideAppBubble_collapseIfSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ public void testShowOrHideNotesBubble_collapseIfSelected() {
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(
- Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0));
+ Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), mUser0));
assertThat(mBubbleController.isStackExpanded()).isTrue();
// Calling this while the app bubble is expanded should collapse the stack
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(
- Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0));
+ Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), mUser0));
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(mUser0);
}
@Test
- public void testShowOrHideAppBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() {
+ public void testShowOrHideNotesBubbleWithNonPrimaryUser_bubbleCollapsedWithExpectedUser() {
UserHandle user10 = createUserHandle(/* userId = */ 10);
- String appBubbleKey = Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), user10);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon);
- assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ String notesKey = Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), user10);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, user10, mNotesBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(notesKey);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
// Calling this while the app bubble is expanded should collapse the stack
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, user10, mNotesBubbleIcon);
- assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(notesKey);
assertThat(mBubbleController.isStackExpanded()).isFalse();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(user10);
}
@Test
- public void testShowOrHideAppBubbleOnUser10AndThenUser0_user0BubbleExpanded() {
+ public void testShowOrHideNotesBubbleOnUser10AndThenUser0_user0BubbleExpanded() {
UserHandle user10 = createUserHandle(/* userId = */ 10);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, user10, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, user10, mNotesBubbleIcon);
- String appBubbleUser0Key = Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ String notesBubbleUser0Key = Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(),
+ mUser0);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
- assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleUser0Key);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(notesBubbleUser0Key);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles()).hasSize(2);
assertThat(mBubbleData.getBubbles().get(0).getUser()).isEqualTo(mUser0);
@@ -2059,63 +2055,64 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void testShowOrHideAppBubble_selectIfNotSelected() {
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ public void testShowOrHideNotesBubble_selectIfNotSelected() {
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(mBubbleEntry.getKey());
assertThat(mBubbleController.isStackExpanded()).isTrue();
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(
- Bubble.getAppBubbleKeyForApp(mContext.getPackageName(), mUser0));
+ Bubble.getNoteBubbleKeyForApp(mContext.getPackageName(), mUser0));
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(2);
}
@Test
- public void testShowOrHideAppBubble_addsFromOverflow() {
- String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
-
+ public void testShowOrHideNotesBubble_addsFromOverflow() {
+ String noteBubbleKey = Bubble.getNoteBubbleKeyForApp(mNotesBubbleIntent.getPackage(),
+ mUser0);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
// Collapse the stack so we don't need to wait for the dismiss animation in the test
mBubbleController.collapseStack();
// Dismiss the app bubble so it's in the overflow
- mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull();
+ mBubbleController.dismissBubble(noteBubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(noteBubbleKey)).isNotNull();
// Calling this while collapsed will re-add and expand the app bubble
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
- assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(noteBubbleKey);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ assertThat(mBubbleData.getOverflowBubbleWithKey(noteBubbleKey)).isNull();
}
@Test
- public void testShowOrHideAppBubble_updateExistedBubbleInOverflow_updateIntentInBubble() {
- String appBubbleKey = Bubble.getAppBubbleKeyForApp(mAppBubbleIntent.getPackage(), mUser0);
- mBubbleController.showOrHideAppBubble(mAppBubbleIntent, mUser0, mAppBubbleIcon);
+ public void testShowOrHideNotesBubble_updateExistedBubbleInOverflow_updateIntentInBubble() {
+ String noteBubbleKey = Bubble.getNoteBubbleKeyForApp(mNotesBubbleIntent.getPackage(),
+ mUser0);
+ mBubbleController.showOrHideNotesBubble(mNotesBubbleIntent, mUser0, mNotesBubbleIcon);
// Collapse the stack so we don't need to wait for the dismiss animation in the test
mBubbleController.collapseStack();
// Dismiss the app bubble so it's in the overflow
- mBubbleController.dismissBubble(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNotNull();
+ mBubbleController.dismissBubble(noteBubbleKey, Bubbles.DISMISS_USER_GESTURE);
+ assertThat(mBubbleData.getOverflowBubbleWithKey(noteBubbleKey)).isNotNull();
// Modify the intent to include new extras.
- Intent newAppBubbleIntent = new Intent(mContext, BubblesTestActivity.class)
+ Intent newIntent = new Intent(mContext, BubblesTestActivity.class)
.setPackage(mContext.getPackageName())
.putExtra("hello", "world");
// Calling this while collapsed will re-add and expand the app bubble
- mBubbleController.showOrHideAppBubble(newAppBubbleIntent, mUser0, mAppBubbleIcon);
- assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(appBubbleKey);
+ mBubbleController.showOrHideNotesBubble(newIntent, mUser0, mNotesBubbleIcon);
+ assertThat(mBubbleData.getSelectedBubble().getKey()).isEqualTo(noteBubbleKey);
assertThat(mBubbleController.isStackExpanded()).isTrue();
assertThat(mBubbleData.getBubbles().size()).isEqualTo(1);
- assertThat(mBubbleData.getBubbles().get(0).getAppBubbleIntent()).extras().string(
- "hello").isEqualTo("world");
- assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
+ assertThat(mBubbleData.getBubbles().get(0).getAppBubbleIntent()
+ .getStringExtra("hello")).isEqualTo("world");
+ assertThat(mBubbleData.getOverflowBubbleWithKey(noteBubbleKey)).isNull();
}
@Test
@@ -2143,9 +2140,9 @@ public class BubblesTest extends SysuiTestCase {
assertFalse("FLAG_NO_DISMISS Notifs should be non-dismissable", bubble.isDismissable());
}
+ @DisableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void registerBubbleBarListener_barDisabled_largeScreen_shouldBeIgnored() {
- mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
@@ -2161,9 +2158,9 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void registerBubbleBarListener_barEnabled_smallScreen_shouldBeIgnored() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(false);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
@@ -2179,9 +2176,9 @@ public class BubblesTest extends SysuiTestCase {
assertThat(mBubbleController.getStackView().getBubbleCount()).isEqualTo(1);
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void registerBubbleBarListener_switchToBarAndBackToStack() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
@@ -2211,9 +2208,9 @@ public class BubblesTest extends SysuiTestCase {
assertBubbleIsInflatedForStack(mBubbleData.getOverflow());
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void registerBubbleBarListener_switchToBarWhileExpanded() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
@@ -2238,9 +2235,9 @@ public class BubblesTest extends SysuiTestCase {
assertThat(layerView.isExpanded()).isTrue();
}
+ @DisableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void switchBetweenBarAndStack_noBubbles_shouldBeIgnored() {
- mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
assertFalse(mBubbleController.hasBubbles());
@@ -2256,9 +2253,9 @@ public class BubblesTest extends SysuiTestCase {
assertNoBubbleContainerViews();
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void bubbleBarBubbleExpandedAndCollapsed() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
@@ -2277,7 +2274,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void dragBubbleBarBubble_selectedBubble_expandedViewCollapsesDuringDrag() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2306,7 +2302,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void dragBubbleBarBubble_unselectedBubble_expandedViewCollapsesDuringDrag() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2335,7 +2330,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void dismissBubbleBarBubble_selected_selectsAndExpandsNext() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2359,7 +2353,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void dismissBubbleBarBubble_unselected_selectionDoesNotChange() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2407,9 +2400,9 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleController).onSensitiveNotificationProtectionStateChanged(false);
}
+ @EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void setBubbleBarLocation_listenerNotified() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
@@ -2421,9 +2414,9 @@ public class BubblesTest extends SysuiTestCase {
BubbleBarLocation.LEFT);
}
+ @DisableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void setBubbleBarLocation_barDisabled_shouldBeIgnored() {
- mBubbleProperties.mIsBubbleBarEnabled = false;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
@@ -2496,7 +2489,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_addBubble() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2510,7 +2502,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_updateBubble() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2527,7 +2518,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_dragSelectedBubbleToDismiss() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2554,7 +2544,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_dragOtherBubbleToDismiss() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2579,7 +2568,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_dragBarToDismiss() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
// Not a user gesture, should not log an event
@@ -2594,7 +2582,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_expandAndCollapse() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2614,7 +2601,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_autoExpandingBubble() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2630,7 +2616,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_switchBubble() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2656,7 +2641,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_openOverflow() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2672,7 +2656,6 @@ public class BubblesTest extends SysuiTestCase {
@EnableFlags(FLAG_ENABLE_BUBBLE_BAR)
@Test
public void testEventLogging_bubbleBar_fromOverflowToBar() {
- mBubbleProperties.mIsBubbleBarEnabled = true;
mPositioner.setIsLargeScreen(true);
FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener();
mBubbleController.registerBubbleStateListener(bubbleStateListener);
@@ -2902,16 +2885,4 @@ public class BubblesTest extends SysuiTestCase {
@Override
public void onItemDraggedOutsideBubbleBarDropZone() {}
}
-
- private static class FakeBubbleProperties implements BubbleProperties {
- boolean mIsBubbleBarEnabled = false;
-
- @Override
- public boolean isBubbleBarEnabled() {
- return mIsBubbleBarEnabled;
- }
-
- @Override
- public void refresh() {}
- }
}
diff --git a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
index 025f556991f2..80254d58781a 100644
--- a/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/android/view/WindowManagerKosmos.kt
@@ -24,4 +24,6 @@ val Kosmos.fakeWindowManager by Kosmos.Fixture { FakeWindowManager(applicationCo
val Kosmos.mockWindowManager: WindowManager by Kosmos.Fixture { mock(WindowManager::class.java) }
+val Kosmos.mockIWindowManager: IWindowManager by Kosmos.Fixture { mock(IWindowManager::class.java) }
+
var Kosmos.windowManager: WindowManager by Kosmos.Fixture { mockWindowManager }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
index ef9bd8282090..5793695a7f01 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryKosmos.kt
@@ -62,6 +62,7 @@ val Kosmos.defaultKeyguardBlueprint by
defaultSettingsPopupMenuSection = mock(),
defaultStatusBarSection = mock(),
defaultNotificationStackScrollLayoutSection = mock(),
+ aodPromotedNotificationSection = mock(),
aodNotificationIconsSection = mock(),
aodBurnInSection = mock(),
clockSection = keyguardClockSection,
@@ -69,7 +70,6 @@ val Kosmos.defaultKeyguardBlueprint by
keyguardSliceViewSection = mock(),
udfpsAccessibilityOverlaySection = mock(),
accessibilityActionsSection = mock(),
- aodPromotedNotificationSection = mock(),
)
}
@@ -84,6 +84,7 @@ val Kosmos.splitShadeBlueprint by
defaultStatusBarSection = mock(),
splitShadeNotificationStackScrollLayoutSection = mock(),
splitShadeGuidelines = mock(),
+ aodPromotedNotificationSection = mock(),
aodNotificationIconsSection = mock(),
aodBurnInSection = mock(),
clockSection = keyguardClockSection,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsContent.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsContent.kt
new file mode 100644
index 000000000000..84a736439e08
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsContent.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+
+@Composable
+fun FakeTileDetailsContent() {
+ Text(
+ text = "Fake details content",
+ textAlign = TextAlign.Center,
+ fontWeight = FontWeight.ExtraBold,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
index 555f019822c2..4f8d5a14e390 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeTileDetailsViewModel.kt
@@ -16,24 +16,11 @@
package com.android.systemui.qs
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextAlign
import com.android.systemui.plugins.qs.TileDetailsViewModel
class FakeTileDetailsViewModel(var tileSpec: String?) : TileDetailsViewModel() {
private var _clickOnSettingsButton = 0
- @Composable
- override fun GetContentView() {
- Text(
- text = "Fake details content",
- textAlign = TextAlign.Center,
- fontWeight = FontWeight.ExtraBold,
- )
- }
-
override fun clickOnSettingsButton() {
_clickOnSettingsButton++
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 65bfafbfa9b0..7a9b052481cb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -33,6 +33,7 @@ import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
@@ -91,5 +92,6 @@ val Kosmos.sceneContainerStartable by Fixture {
activityTransitionAnimator = activityTransitionAnimator,
shadeModeInteractor = shadeModeInteractor,
tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"),
+ trustInteractor = trustInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
index 1e304d979e03..ab61a3ef4c3f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.call.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.activityStarter
@@ -26,6 +27,7 @@ import com.android.systemui.util.time.fakeSystemClock
val Kosmos.callChipViewModel: CallChipViewModel by
Kosmos.Fixture {
CallChipViewModel(
+ applicationContext,
scope = applicationCoroutineScope,
interactor = callChipInteractor,
systemClock = fakeSystemClock,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
index d0c80c7332b3..878c2deb43b2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.ui.viewmodel
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
@@ -24,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.heads
val Kosmos.notifChipsViewModel: NotifChipsViewModel by
Kosmos.Fixture {
NotifChipsViewModel(
+ applicationContext,
applicationCoroutineScope,
statusBarNotificationChipsInteractor,
headsUpNotificationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
index 8c37bd739bc5..bdcab5fd2eca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.core
import android.content.testableContext
+import android.view.mockIWindowManager
import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.display.data.repository.displayScopeRepository
@@ -84,5 +85,6 @@ val Kosmos.multiDisplayStatusBarStarter by
multiDisplayAutoHideControllerStore,
privacyDotWindowControllerStore,
lightBarControllerStore,
+ mockIWindowManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index c6ae15df6859..63085e178e7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -43,6 +43,7 @@ fun activeNotificationModel(
instanceId: Int? = null,
isGroupSummary: Boolean = false,
packageName: String = "pkg",
+ appName: String = "appName",
contentIntent: PendingIntent? = null,
bucket: Int = BUCKET_UNKNOWN,
callType: CallType = CallType.None,
@@ -64,6 +65,7 @@ fun activeNotificationModel(
statusBarChipIconView = statusBarChipIcon,
uid = uid,
packageName = packageName,
+ appName = appName,
contentIntent = contentIntent,
instanceId = instanceId,
isGroupSummary = isGroupSummary,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9846df..0acf98fec054 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -16,11 +16,16 @@
package com.android.systemui.statusbar.notification.domain.interactor
+import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
val Kosmos.renderNotificationListInteractor by
Kosmos.Fixture {
- RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+ RenderNotificationListInteractor(
+ activeNotificationListRepository,
+ sectionStyleProvider,
+ applicationContext,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
index f4e74fe0e6bb..923b36d4f2cf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/phone/ongoingcall/shared/model/OngoingCallModelBuilder.kt
@@ -26,5 +26,14 @@ fun inCallModel(
notificationIcon: StatusBarIconView? = null,
intent: PendingIntent? = null,
notificationKey: String = "test",
+ appName: String = "",
promotedContent: PromotedNotificationContentModel? = null,
-) = OngoingCallModel.InCall(startTimeMs, notificationIcon, intent, notificationKey, promotedContent)
+) =
+ OngoingCallModel.InCall(
+ startTimeMs,
+ notificationIcon,
+ intent,
+ notificationKey,
+ appName,
+ promotedContent,
+ )
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index 383e75bb5122..e202d0ecfa23 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -151,6 +151,10 @@ android.os.Looper
android.os.Message
android.os.MessageQueue
android.os.MessageQueue_ravenwood
+android.os.PerfettoTrace
+android.os.PerfettoTrace$Category
+android.os.PerfettoTrackEventExtra
+android.os.PerfettoTrackEventExtra$NoOpBuilder
android.os.PackageTagsList
android.os.Parcel
android.os.ParcelFileDescriptor
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
index 1bc9c783df76..8e448676c214 100644
--- a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickController.java
@@ -81,6 +81,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
@VisibleForTesting AutoclickSettingsObserver mAutoclickSettingsObserver;
@VisibleForTesting AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
@VisibleForTesting AutoclickIndicatorView mAutoclickIndicatorView;
+ @VisibleForTesting AutoclickTypePanel mAutoclickTypePanel;
private WindowManager mWindowManager;
public AutoclickController(Context context, int userId, AccessibilityTraceManager trace) {
@@ -123,6 +124,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorView = new AutoclickIndicatorView(mContext);
mWindowManager = mContext.getSystemService(WindowManager.class);
+ mAutoclickTypePanel = new AutoclickTypePanel(mContext, mWindowManager);
+
+ mAutoclickTypePanel.show();
mWindowManager.addView(mAutoclickIndicatorView, mAutoclickIndicatorView.getLayoutParams());
}
@@ -167,6 +171,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
mAutoclickIndicatorScheduler.cancel();
mAutoclickIndicatorScheduler = null;
mWindowManager.removeView(mAutoclickIndicatorView);
+ mAutoclickTypePanel.hide();
}
}
@@ -310,7 +315,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
if (mAutoclickIndicatorScheduler != null) {
mAutoclickIndicatorScheduler.updateCursorAreaSize(size);
}
- mClickScheduler.updateMovementSlope(size);
+ mClickScheduler.updateMovementSlop(size);
}
if (mAutoclickIgnoreMinorCursorMovementSettingUri.equals(uri)) {
@@ -400,9 +405,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
* to be discarded as noise. Anchor is the position of the last MOVE event that was not
* considered noise.
*/
- private static final double DEFAULT_MOVEMENT_SLOPE = 20f;
+ private static final double DEFAULT_MOVEMENT_SLOP = 20f;
- private double mMovementSlope = DEFAULT_MOVEMENT_SLOPE;
+ private double mMovementSlop = DEFAULT_MOVEMENT_SLOP;
/** Whether the minor cursor movement should be ignored. */
private boolean mIgnoreMinorCursorMovement = AUTOCLICK_IGNORE_MINOR_CURSOR_MOVEMENT_DEFAULT;
@@ -500,6 +505,11 @@ public class AutoclickController extends BaseEventStreamTransformation {
mMetaState = state;
}
+ @VisibleForTesting
+ int getMetaStateForTesting() {
+ return mMetaState;
+ }
+
/**
* Updates delay that should be used when scheduling clicks. The delay will be used only for
* clicks scheduled after this point (pending click tasks are not affected).
@@ -589,19 +599,19 @@ public class AutoclickController extends BaseEventStreamTransformation {
float deltaX = mAnchorCoords.x - event.getX(pointerIndex);
float deltaY = mAnchorCoords.y - event.getY(pointerIndex);
double delta = Math.hypot(deltaX, deltaY);
- double slope =
+ double slop =
((Flags.enableAutoclickIndicator() && mIgnoreMinorCursorMovement)
- ? mMovementSlope
- : DEFAULT_MOVEMENT_SLOPE);
- return delta > slope;
+ ? mMovementSlop
+ : DEFAULT_MOVEMENT_SLOP);
+ return delta > slop;
}
public void setIgnoreMinorCursorMovement(boolean ignoreMinorCursorMovement) {
mIgnoreMinorCursorMovement = ignoreMinorCursorMovement;
}
- private void updateMovementSlope(double slope) {
- mMovementSlope = slope;
+ private void updateMovementSlop(double slop) {
+ mMovementSlop = slop;
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
new file mode 100644
index 000000000000..1ba574559918
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/autoclick/AutoclickTypePanel.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.accessibility.autoclick;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.R;
+
+public class AutoclickTypePanel {
+
+ private final String TAG = AutoclickTypePanel.class.getSimpleName();
+
+ private final Context mContext;
+
+ private final View mContentView;
+
+ private final WindowManager mWindowManager;
+
+ public AutoclickTypePanel(Context context, WindowManager windowManager) {
+ mContext = context;
+ mWindowManager = windowManager;
+
+ mContentView = LayoutInflater.from(context).inflate(
+ R.layout.accessibility_autoclick_type_panel, null);
+ }
+
+ public void show() {
+ mWindowManager.addView(mContentView, getLayoutParams());
+ }
+
+ public void hide() {
+ mWindowManager.removeView(mContentView);
+ }
+
+ /**
+ * Retrieves the layout params for AutoclickIndicatorView, used when it's added to the Window
+ * Manager.
+ */
+ @NonNull
+ private WindowManager.LayoutParams getLayoutParams() {
+ final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
+ layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
+ layoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ layoutParams.setFitInsetsTypes(WindowInsets.Type.statusBars());
+ layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ layoutParams.format = PixelFormat.TRANSLUCENT;
+ layoutParams.setTitle(AutoclickTypePanel.class.getSimpleName());
+ layoutParams.accessibilityTitle =
+ mContext.getString(R.string.accessibility_autoclick_type_settings_panel_title);
+ layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+ layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ // TODO(b/388847771): Compute position based on user interaction.
+ layoutParams.x = 15;
+ layoutParams.y = 90;
+ layoutParams.gravity = Gravity.END | Gravity.BOTTOM;
+
+ return layoutParams;
+ }
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 02a8f6218468..521f676a6703 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -47,6 +47,7 @@ import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.KeyguardManager;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.ecm.EnhancedConfirmationManager;
@@ -302,8 +303,17 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"create associations");
- mAssociationRequestsProcessor.processNewAssociationRequest(
- request, packageName, userId, callback);
+ if (request.isSkipRoleGrant()) {
+ checkCallerCanSkipRoleGrant();
+ mAssociationRequestsProcessor.createAssociation(userId, packageName,
+ /* macAddress= */ null, request.getDisplayName(),
+ request.getDeviceProfile(), /* associatedDevice= */ null,
+ request.isSelfManaged(), callback, /* resultReceiver= */ null,
+ request.getDeviceIcon(), /* skipRoleGrant= */ true);
+ } else {
+ mAssociationRequestsProcessor.processNewAssociationRequest(
+ request, packageName, userId, callback);
+ }
}
@Override
@@ -669,7 +679,7 @@ public class CompanionDeviceManagerService extends SystemService {
final MacAddress macAddressObj = MacAddress.fromString(macAddress);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddressObj,
- null, null, null, false, null, null, null);
+ null, null, null, false, null, null, null, false);
}
private void checkCanCallNotificationApi(String callingPackage, int userId) {
@@ -684,6 +694,19 @@ public class CompanionDeviceManagerService extends SystemService {
"App must have an association before calling this API");
}
+ private void checkCallerCanSkipRoleGrant() {
+ final KeyguardManager keyguardManager =
+ getContext().getSystemService(KeyguardManager.class);
+ if (keyguardManager != null && keyguardManager.isKeyguardSecure()) {
+ throw new SecurityException("Skipping CDM role grant requires insecure keyguard.");
+ }
+ if (getContext().checkCallingPermission(ASSOCIATE_COMPANION_DEVICES)
+ != PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "Skipping CDM role grant requires ASSOCIATE_COMPANION_DEVICES permission.");
+ }
+ }
+
@Override
public boolean canPairWithoutPrompt(String packageName, String macAddress, int userId) {
final AssociationInfo association =
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 3508f2ffc4c4..e7d1460aa66a 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -106,8 +106,9 @@ class CompanionDeviceShellCommand extends ShellCommand {
boolean selfManaged = getNextBooleanArg();
final MacAddress macAddress = MacAddress.fromString(address);
mAssociationRequestsProcessor.createAssociation(userId, packageName, macAddress,
- deviceProfile, deviceProfile, /* associatedDevice */ null, selfManaged,
- /* callback */ null, /* resultReceiver */ null, /* deviceIcon */ null);
+ deviceProfile, deviceProfile, /* associatedDevice= */ null, selfManaged,
+ /* callback= */ null, /* resultReceiver= */ null,
+ /* deviceIcon= */ null, /* skipRoleGrant= */ false);
}
break;
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 899b302316f9..8c2c63cbbd84 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -282,8 +282,8 @@ public class AssociationRequestsProcessor {
Binder.withCleanCallingIdentity(() -> {
createAssociation(userId, packageName, macAddress, request.getDisplayName(),
request.getDeviceProfile(), request.getAssociatedDevice(),
- request.isSelfManaged(),
- callback, resultReceiver, request.getDeviceIcon());
+ request.isSelfManaged(), callback, resultReceiver, request.getDeviceIcon(),
+ /* skipRoleGrant= */ false);
});
}
@@ -294,7 +294,8 @@ public class AssociationRequestsProcessor {
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
boolean selfManaged, @Nullable IAssociationRequestCallback callback,
- @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon) {
+ @Nullable ResultReceiver resultReceiver, @Nullable Icon deviceIcon,
+ boolean skipRoleGrant) {
final int id = mAssociationStore.getNextId();
final long timestamp = System.currentTimeMillis();
@@ -303,8 +304,17 @@ public class AssociationRequestsProcessor {
selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
/* pending */ false, timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0,
deviceIcon, /* deviceId */ null);
- // Add role holder for association (if specified) and add new association to store.
- maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+
+ if (skipRoleGrant) {
+ Slog.i(TAG, "Created association for " + association.getDeviceProfile() + " and userId="
+ + association.getUserId() + ", packageName="
+ + association.getPackageName() + " without granting role");
+ mAssociationStore.addAssociation(association);
+ sendCallbackAndFinish(association, callback, resultReceiver);
+ } else {
+ // Add role holder for association (if specified) and add new association to store.
+ maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
+ }
}
/**
diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
index df47c98d6433..89c9d690a82c 100644
--- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
+++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java
@@ -27,10 +27,6 @@ import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
-import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_CONTENT;
import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUCTURE;
@@ -74,6 +70,7 @@ import android.util.Log;
import android.util.Slog;
import android.view.IWindowManager;
import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -333,10 +330,10 @@ public class ContextualSearchManagerService extends SystemService {
isManagedProfileVisible = true;
}
}
+ final String csPackage = Objects.requireNonNull(launchIntent.getPackage());
+ final int csUid = mPackageManager.getPackageUid(csPackage, /* flags */ 0L, userId);
if (isAssistDataAllowed) {
try {
- final String csPackage = Objects.requireNonNull(launchIntent.getPackage());
- final int csUid = mPackageManager.getPackageUid(csPackage, 0, 0);
mAssistDataRequester.requestAssistData(
activityTokens,
/* fetchData */ true,
@@ -350,17 +347,8 @@ public class ContextualSearchManagerService extends SystemService {
Log.e(TAG, "Could not request assist data", e);
}
}
- final ScreenCapture.ScreenshotHardwareBuffer shb;
- if (mWmInternal != null) {
- shb = mWmInternal.takeAssistScreenshot(Set.of(
- TYPE_STATUS_BAR,
- TYPE_NAVIGATION_BAR,
- TYPE_NAVIGATION_BAR_PANEL,
- TYPE_POINTER));
- } else {
- if (DEBUG) Log.w(TAG, "Can't capture contextual screenshot: mWmInternal is null");
- shb = null;
- }
+ final ScreenshotHardwareBuffer shb = mWmInternal.takeContextualSearchScreenshot(
+ (Flags.contextualSearchWindowLayer() ? csUid : -1));
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
@@ -509,15 +497,17 @@ public class ContextualSearchManagerService extends SystemService {
bundle.putParcelable(ContextualSearchManager.EXTRA_TOKEN, mToken);
// We get take the screenshot with the system server's identity because the system
// server has READ_FRAME_BUFFER permission to get the screenshot.
+ final int callingUid = Binder.getCallingUid();
Binder.withCleanCallingIdentity(() -> {
- if (mWmInternal != null) {
+ final ScreenshotHardwareBuffer shb =
+ mWmInternal.takeContextualSearchScreenshot(
+ (Flags.contextualSearchWindowLayer() ? callingUid : -1));
+ final Bitmap bm = shb != null ? shb.asBitmap() : null;
+ if (bm != null) {
bundle.putParcelable(ContextualSearchManager.EXTRA_SCREENSHOT,
- mWmInternal.takeAssistScreenshot(Set.of(
- TYPE_STATUS_BAR,
- TYPE_NAVIGATION_BAR,
- TYPE_NAVIGATION_BAR_PANEL,
- TYPE_POINTER))
- .asBitmap().asShared());
+ bm.asShared());
+ bundle.putBoolean(ContextualSearchManager.EXTRA_FLAG_SECURE_FOUND,
+ shb.containsSecureLayers());
}
try {
callback.onResult(
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 7a5b8660ef7c..cf0d7e72ba8b 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -53,6 +53,7 @@ import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
import com.android.server.os.TombstoneProtos.Tombstone;
+import libcore.io.IoUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -154,6 +155,10 @@ public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
+ if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
+ return;
+ }
+
// Log boot events in the background to avoid blocking the main thread with I/O
new Thread() {
@Override
@@ -219,6 +224,8 @@ public class BootReceiver extends BroadcastReceiver {
} catch (Exception e) {
Slog.wtf(TAG, "Error watching for trace events", e);
return 0; // Unregister the handler.
+ } finally {
+ IoUtils.closeQuietly(fd);
}
return OnFileDescriptorEventListener.EVENT_INPUT;
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 19e7e062758a..ce6e1ba0cfda 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -947,7 +947,6 @@ class StorageManagerService extends IStorageManager.Stub
refreshZramSettings();
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
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 896c9b8d0932..c65981bf0703 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,11 +16,13 @@
package com.android.server;
-import static android.app.Flags.modesApi;
import static android.app.Flags.enableCurrentModeTypeBinderCache;
import static android.app.Flags.enableNightModeBinderCache;
+import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.FORCE_INVERT_TYPE_DARK;
+import static android.app.UiModeManager.FORCE_INVERT_TYPE_OFF;
import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
@@ -33,6 +35,7 @@ import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserHandle.getCallingUserId;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED;
import static android.provider.Settings.Secure.CONTRAST_LEVEL;
import static android.util.TimeUtils.isTimeBetween;
@@ -45,6 +48,7 @@ import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
import android.app.AlarmManager;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
@@ -56,6 +60,7 @@ import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
import android.app.UiModeManager.AttentionModeThemeOverlayType;
+import android.app.UiModeManager.ForceInvertType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -93,6 +98,7 @@ import android.service.vr.IVrStateCallbacks;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseIntArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -256,6 +262,9 @@ final class UiModeManagerService extends SystemService {
@GuardedBy("mLock")
private final SparseArray<Float> mContrasts = new SparseArray<>();
+ @GuardedBy("mLock")
+ private final SparseIntArray mForceInvertStates = new SparseIntArray();
+
public UiModeManagerService(Context context) {
this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
}
@@ -407,9 +416,33 @@ final class UiModeManagerService extends SystemService {
@Override
public void onChange(boolean selfChange, Uri uri) {
updateSystemProperties();
+ updateForceInvertStates();
}
};
+ private final ContentObserver mForceInvertStateObserver = new ContentObserver(mHandler) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ updateForceInvertStates();
+ }
+ };
+
+ private void updateForceInvertStates() {
+ if (!android.view.accessibility.Flags.forceInvertColor()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ if (updateForceInvertStateLocked()) {
+ int forceInvertState = getForceInvertStateLocked();
+ mUiModeManagerCallbacks.get(mCurrentUser, new RemoteCallbackList<>())
+ .broadcast(ignoreRemoteException(
+ callback ->
+ callback.notifyForceInvertStateChanged(forceInvertState)));
+ }
+ }
+ }
+
private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
@Override
public void onChange(boolean selfChange, Uri uri) {
@@ -485,6 +518,12 @@ final class UiModeManagerService extends SystemService {
context.getContentResolver()
.registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
false, mDarkThemeObserver, 0);
+ if (android.view.accessibility.Flags.forceInvertColor()) {
+ context.getContentResolver()
+ .registerContentObserver(
+ Secure.getUriFor(ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED),
+ false, mForceInvertStateObserver, UserHandle.USER_ALL);
+ }
context.getContentResolver().registerContentObserver(
Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
mContrastObserver, UserHandle.USER_ALL);
@@ -919,7 +958,7 @@ final class UiModeManagerService extends SystemService {
@android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
@Override
- public @NightModeCustomReturnType int getNightModeCustomType() {
+ public @NightModeCustomReturnType int getNightModeCustomType() {
getNightModeCustomType_enforcePermission();
synchronized (mLock) {
return mNightModeCustomType;
@@ -1275,6 +1314,14 @@ final class UiModeManagerService extends SystemService {
return getContrastLocked();
}
}
+
+ @Override
+ @ForceInvertType
+ public int getForceInvertState() {
+ synchronized (mLock) {
+ return getForceInvertStateLocked();
+ }
+ }
};
private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
@@ -1358,6 +1405,76 @@ final class UiModeManagerService extends SystemService {
}
/**
+ * Return the force invert for the current user. If not cached, fetch it from the settings.
+ */
+ @GuardedBy("mLock")
+ @ForceInvertType
+ private int getForceInvertStateLocked() {
+ if (mForceInvertStates.indexOfKey(mCurrentUser) < 0) {
+ updateForceInvertStateLocked();
+ }
+ return mForceInvertStates.get(mCurrentUser);
+ }
+
+ /**
+ * Read the force invert setting for the current user and update {@link #mForceInvertStates}
+ * if the contrast changed. Returns true if {@link #mForceInvertStates} was updated.
+ */
+ @GuardedBy("mLock")
+ private boolean updateForceInvertStateLocked() {
+ int forceInvertState = getForceInvertStateInternal();
+ if (mForceInvertStates.get(mCurrentUser) != forceInvertState) {
+ mForceInvertStates.put(mCurrentUser, forceInvertState);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the current state of force invert, which modifies the display colors to
+ * enhance visibility based on the system's dark theme settings and app-specific configurations.
+ *
+ * <p>This method is for informational purposes only. The application does not need to
+ * implement any special handling for force invert; the system applies it automatically.
+ * If you want to prevent force invert from affecting your app, ensure you have defined
+ * both light and dark themes. Force invert is not applied to apps that already adapt
+ * to the user's system theme preference.</p>
+ *
+ * @return The current force invert state, represented by a {@code ForceDarkType} constant.
+ *
+ * @hide
+ */
+ private int getForceInvertStateInternal() {
+ if (!android.view.accessibility.Flags.forceInvertColor()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ if (!isSystemInDarkTheme()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ if (!isForceInvert()) {
+ return FORCE_INVERT_TYPE_OFF;
+ }
+
+ return FORCE_INVERT_TYPE_DARK;
+ }
+
+ private boolean isSystemInDarkTheme() {
+ Context sysUiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+ int sysUiNightMode = sysUiContext.getResources().getConfiguration().uiMode
+ & Configuration.UI_MODE_NIGHT_MASK;
+ return sysUiNightMode == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ private boolean isForceInvert() {
+ return Settings.Secure.getIntForUser(
+ getContext().getContentResolver(),
+ Settings.Secure.ACCESSIBILITY_FORCE_INVERT_COLOR_ENABLED,
+ /* def= */ 0, mCurrentUser) == 1;
+ }
+
+ /**
* Return the contrast for the current user. If not cached, fetch it from the settings.
*/
@GuardedBy("mLock")
@@ -1967,6 +2084,14 @@ final class UiModeManagerService extends SystemService {
sendConfigurationAndStartDreamOrDockAppLocked(category);
}
+ private boolean shouldStartDockApp(Context context, Intent homeIntent) {
+ if (mWatch && !mSetupWizardComplete) {
+ // Do not ever start dock app when setup is not complete on a watch.
+ return false;
+ }
+ return Sandman.shouldStartDockApp(context, homeIntent);
+ }
+
private void sendConfigurationAndStartDreamOrDockAppLocked(String category) {
// Update the configuration but don't send it yet.
mHoldingConfiguration = false;
@@ -1983,7 +2108,7 @@ final class UiModeManagerService extends SystemService {
// activity manager take care of both the start and config
// change.
Intent homeIntent = buildHomeIntent(category);
- if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
+ if (shouldStartDockApp(getContext(), homeIntent)) {
try {
int result = ActivityTaskManager.getService().startActivityWithConfig(
null, getContext().getBasePackageName(),
diff --git a/services/core/java/com/android/server/ZramMaintenance.java b/services/core/java/com/android/server/ZramMaintenance.java
index cdb48122e321..099e5b3fe440 100644
--- a/services/core/java/com/android/server/ZramMaintenance.java
+++ b/services/core/java/com/android/server/ZramMaintenance.java
@@ -24,11 +24,15 @@ import android.content.ComponentName;
import android.content.Context;
import android.os.IBinder;
import android.os.IMmd;
+import android.os.PersistableBundle;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.provider.DeviceConfig;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.time.Duration;
/**
@@ -46,43 +50,45 @@ 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;
+ @VisibleForTesting
+ public static final int JOB_ID = 375432472;
private static final ComponentName sZramMaintenance =
new ComponentName("android", ZramMaintenance.class.getName());
+ @VisibleForTesting
+ public static final String KEY_CHECK_STATUS = "check_status";
+ private static final String SYSTEM_PROPERTY_PREFIX = "mm.";
private static final String FIRST_DELAY_SECONDS_PROP =
- "mm.zram.maintenance.first_delay_seconds";
+ "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";
+ "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";
+ "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";
+ "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);
+ new Thread("ZramMaintenance") {
+ @Override
+ public void run() {
+ try {
+ IBinder binder = ServiceManager.getService("mmd");
+ IMmd mmd = IMmd.Stub.asInterface(binder);
+ startJob(ZramMaintenance.this, params, mmd);
+ } finally {
+ jobFinished(params, false);
+ }
}
- } else {
- Slog.w(TAG, "binder not found");
- }
- Duration delay = Duration.ofSeconds(SystemProperties.getLong(PERIODIC_DELAY_SECONDS_PROP,
- DEFAULT_PERIODIC_DELAY_SECONDS));
- scheduleZramMaintenance(this, delay);
+ }.start();
return true;
}
@@ -92,27 +98,75 @@ public class ZramMaintenance extends JobService {
}
/**
+ * This is public to test ZramMaintenance logic.
+ *
+ * <p>
+ * We need to pass mmd as parameter because we can't mock "IMmd.Stub.asInterface".
+ *
+ * <p>
+ * Since IMmd.isZramMaintenanceSupported() is blocking call, this method should be executed on
+ * a worker thread.
+ */
+ @VisibleForTesting
+ public static void startJob(Context context, JobParameters params, IMmd mmd) {
+ boolean checkStatus = params.getExtras().getBoolean(KEY_CHECK_STATUS);
+ if (mmd != null) {
+ try {
+ if (checkStatus && !mmd.isZramMaintenanceSupported()) {
+ Slog.i(TAG, "zram maintenance is not supported");
+ return;
+ }
+ // Status check is required before the first doZramMaintenanceAsync() call once.
+ checkStatus = false;
+
+ mmd.doZramMaintenanceAsync();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to binder call to mmd", e);
+ }
+ } else {
+ Slog.w(TAG, "binder not found");
+ }
+ Duration delay = Duration.ofSeconds(getLongProperty(PERIODIC_DELAY_SECONDS_PROP,
+ DEFAULT_PERIODIC_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, checkStatus);
+ }
+
+ /**
* 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);
+ getLongProperty(FIRST_DELAY_SECONDS_PROP, DEFAULT_FIRST_DELAY_SECONDS));
+ scheduleZramMaintenance(context, delay, true);
}
- private static void scheduleZramMaintenance(Context context, Duration delay) {
+ private static void scheduleZramMaintenance(Context context, Duration delay,
+ boolean checkStatus) {
JobScheduler js = context.getSystemService(JobScheduler.class);
if (js != null) {
+ final PersistableBundle bundle = new PersistableBundle();
+ bundle.putBoolean(KEY_CHECK_STATUS, checkStatus);
js.schedule(new JobInfo.Builder(JOB_ID, sZramMaintenance)
.setMinimumLatency(delay.toMillis())
.setRequiresDeviceIdle(
- SystemProperties.getBoolean(REQUIRE_DEVICE_IDLE_PROP,
+ getBooleanProperty(REQUIRE_DEVICE_IDLE_PROP,
DEFAULT_REQUIRE_DEVICE_IDLE))
.setRequiresBatteryNotLow(
- SystemProperties.getBoolean(REQUIRE_BATTERY_NOT_LOW_PROP,
+ getBooleanProperty(REQUIRE_BATTERY_NOT_LOW_PROP,
DEFAULT_REQUIRE_BATTERY_NOT_LOW))
+ .setExtras(bundle)
.build());
}
}
+
+ private static long getLongProperty(String name, long defaultValue) {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getLong(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
+
+ private static boolean getBooleanProperty(String name, boolean defaultValue) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_MM, name,
+ SystemProperties.getBoolean(SYSTEM_PROPERTY_PREFIX + name, defaultValue));
+ }
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index cbebc905796c..c237897f1229 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -4279,7 +4279,6 @@ final class ActivityManagerShellCommand extends ShellCommand {
}
int runClearBadProcess(PrintWriter pw) throws RemoteException {
- final String processName = getNextArgRequired();
int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
@@ -4290,6 +4289,7 @@ final class ActivityManagerShellCommand extends ShellCommand {
return -1;
}
}
+ final String processName = getNextArgRequired();
if (userId == UserHandle.USER_CURRENT) {
userId = mInternal.getCurrentUserId();
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 27e9e44f1090..e0fbaf43ea43 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -31,6 +31,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.os.PowerWhitelistManager.REASON_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.REASON_LOCKED_BOOT_COMPLETED;
import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
@@ -3904,10 +3905,6 @@ class UserController implements Handler.Callback {
return mService.mWindowManager;
}
- ActivityTaskManagerInternal getActivityTaskManagerInternal() {
- return mService.mAtmInternal;
- }
-
void activityManagerOnUserStopped(@UserIdInt int userId) {
LocalServices.getService(ActivityTaskManagerInternal.class).onUserStopped(userId);
}
@@ -4122,40 +4119,25 @@ class UserController implements Handler.Callback {
}
void lockDeviceNowAndWaitForKeyguardShown() {
- if (getWindowManager().isKeyguardLocked()) {
- Slogf.w(TAG, "Not locking the device since the keyguard is already locked");
- return;
- }
-
final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
t.traceBegin("lockDeviceNowAndWaitForKeyguardShown");
final CountDownLatch latch = new CountDownLatch(1);
- ActivityTaskManagerInternal.ScreenObserver screenObserver =
- new ActivityTaskManagerInternal.ScreenObserver() {
- @Override
- public void onAwakeStateChanged(boolean isAwake) {
-
- }
-
- @Override
- public void onKeyguardStateChanged(boolean isShowing) {
- if (isShowing) {
- latch.countDown();
- }
- }
- };
-
- getActivityTaskManagerInternal().registerScreenObserver(screenObserver);
- getWindowManager().lockDeviceNow();
+ Bundle bundle = new Bundle();
+ bundle.putBinder(LOCK_ON_USER_SWITCH_CALLBACK, new IRemoteCallback.Stub() {
+ public void sendResult(Bundle data) {
+ latch.countDown();
+ }
+ });
+ getWindowManager().lockNow(bundle);
try {
if (!latch.await(20, TimeUnit.SECONDS)) {
- throw new RuntimeException("Keyguard is not shown in 20 seconds");
+ throw new RuntimeException("User controller expected a callback while waiting "
+ + "to show the keyguard. Timed out after 20 seconds.");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
- getActivityTaskManagerInternal().unregisterScreenObserver(screenObserver);
t.traceEnd();
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index e8a222625b1b..32c4e9b1727e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -551,6 +551,11 @@ public class AppOpsService extends IAppOpsService.Stub {
@VisibleForTesting
final Constants mConstants;
+ /**
+ * Some processes in the user may still be running when trying to drop the user's state
+ */
+ private static final long REMOVE_USER_DELAY = 5000L;
+
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
final class UidState {
public final int uid;
@@ -6820,14 +6825,17 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void removeUser(int userHandle) throws RemoteException {
checkSystemUid("removeUser");
- synchronized (AppOpsService.this) {
- final int tokenCount = mOpUserRestrictions.size();
- for (int i = tokenCount - 1; i >= 0; i--) {
- ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
- opRestrictions.removeUser(userHandle);
+ mHandler.postDelayed(() -> {
+ Slog.i(TAG, "Removing user " + userHandle + " from AppOpsService");
+ synchronized (AppOpsService.this) {
+ final int tokenCount = mOpUserRestrictions.size();
+ for (int i = tokenCount - 1; i >= 0; i--) {
+ ClientUserRestrictionState opRestrictions = mOpUserRestrictions.valueAt(i);
+ opRestrictions.removeUser(userHandle);
+ }
+ removeUidsForUserLocked(userHandle);
}
- removeUidsForUserLocked(userHandle);
- }
+ }, REMOVE_USER_DELAY);
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index c739118194e5..6fab7e620277 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -164,6 +164,7 @@ class PreAuthInfo {
Slog.d(TAG, "Package: " + opPackageName
+ " Sensor ID: " + sensor.id
+ " Modality: " + sensor.modality
+ + " User id: " + effectiveUserId
+ " Status: " + status);
// A sensor with privacy enabled will still be eligible to
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index 373287d76442..a1e8f08db0a6 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -61,7 +61,7 @@ public class DisplayManagerFlags {
private final FlagState mDisplayTopology = new FlagState(
Flags.FLAG_DISPLAY_TOPOLOGY,
- Flags::displayTopology);
+ DesktopExperienceFlags.DISPLAY_TOPOLOGY::isTrue);
private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
@@ -267,7 +267,7 @@ public class DisplayManagerFlags {
private final FlagState mBaseDensityForExternalDisplays = new FlagState(
Flags.FLAG_BASE_DENSITY_FOR_EXTERNAL_DISPLAYS,
- Flags::baseDensityForExternalDisplays
+ DesktopExperienceFlags.BASE_DENSITY_FOR_EXTERNAL_DISPLAYS::isTrue
);
private final FlagState mFramerateOverrideTriggersRrCallbacks = new FlagState(
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index fb5ce5b4e5fa..41f58ae76a4d 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -809,7 +809,7 @@ final class KeyGestureController {
if (firstDown) {
handleKeyGesture(deviceId, new int[]{KeyEvent.KEYCODE_FULLSCREEN},
/* modifierState = */0,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId, focusedToken,
/* flags = */0, /* appLaunchData = */null);
}
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
index 3eb38a7029e6..b13dee530ee2 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider2.java
@@ -56,8 +56,8 @@ import java.util.stream.Stream;
*/
/* package */ class SystemMediaRoute2Provider2 extends SystemMediaRoute2Provider {
- private static final String ROUTE_ID_PREFIX_SYSTEM = "SYSTEM";
- private static final String ROUTE_ID_SYSTEM_SEPARATOR = ".";
+ private static final String UNIQUE_SYSTEM_ID_PREFIX = "SYSTEM";
+ private static final String UNIQUE_SYSTEM_ID_SEPARATOR = "-";
private final PackageManager mPackageManager;
@@ -67,6 +67,10 @@ import java.util.stream.Stream;
@GuardedBy("mLock")
private final Map<String, ProviderProxyRecord> mProxyRecords = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private final Map<String, SystemMediaSessionRecord> mSessionOriginalIdToSessionRecord =
+ new ArrayMap<>();
+
/**
* Maps package names to corresponding sessions maintained by {@link MediaRoute2ProviderService
* provider services}.
@@ -150,7 +154,7 @@ import java.util.stream.Stream;
if (currentProxyRecord != null) {
currentProxyRecord.releaseSession(
requestId, existingSession.getOriginalId());
- existingSessionRecord.removeSelfFromSessionMap();
+ existingSessionRecord.removeSelfFromSessionMaps();
}
}
}
@@ -240,6 +244,24 @@ import java.util.stream.Stream;
super.setRouteVolume(requestId, routeOriginalId, volume);
}
+ @Override
+ public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
+ if (SYSTEM_SESSION_ID.equals(sessionOriginalId)) {
+ super.setSessionVolume(requestId, sessionOriginalId, volume);
+ return;
+ }
+ synchronized (mLock) {
+ var sessionRecord = mSessionOriginalIdToSessionRecord.get(sessionOriginalId);
+ var proxyRecord = sessionRecord != null ? sessionRecord.getProxyRecord() : null;
+ if (proxyRecord != null) {
+ proxyRecord.mProxy.setSessionVolume(
+ requestId, sessionRecord.getServiceSessionId(), volume);
+ return;
+ }
+ }
+ notifyRequestFailed(requestId, MediaRoute2ProviderService.REASON_ROUTE_NOT_AVAILABLE);
+ }
+
/**
* Returns the uid that corresponds to the given name and user handle, or {@link
* Process#INVALID_UID} if a uid couldn't be found.
@@ -376,16 +398,17 @@ import java.util.stream.Stream;
}
/**
- * Equivalent to {@link #asSystemRouteId}, except it takes a unique route id instead of a
- * original id.
+ * Equivalent to {@link #asUniqueSystemId}, except it takes a unique id instead of an original
+ * id.
*/
private static String uniqueIdAsSystemRouteId(String providerId, String uniqueRouteId) {
- return asSystemRouteId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId));
+ return asUniqueSystemId(providerId, MediaRouter2Utils.getOriginalId(uniqueRouteId));
}
/**
* Returns a unique {@link MediaRoute2Info#getOriginalId() original id} for this provider to
- * publish system media routes from {@link MediaRoute2ProviderService provider services}.
+ * publish system media routes and sessions from {@link MediaRoute2ProviderService provider
+ * services}.
*
* <p>This provider will publish system media routes as part of the system routing session.
* However, said routes may also support {@link MediaRoute2Info#FLAG_ROUTING_TYPE_REMOTE remote
@@ -393,12 +416,12 @@ import java.util.stream.Stream;
* we derive a {@link MediaRoute2Info#getOriginalId original id} that is unique among all
* original route ids used by this provider.
*/
- private static String asSystemRouteId(String providerId, String originalRouteId) {
- return ROUTE_ID_PREFIX_SYSTEM
- + ROUTE_ID_SYSTEM_SEPARATOR
+ private static String asUniqueSystemId(String providerId, String originalId) {
+ return UNIQUE_SYSTEM_ID_PREFIX
+ + UNIQUE_SYSTEM_ID_SEPARATOR
+ providerId
- + ROUTE_ID_SYSTEM_SEPARATOR
- + originalRouteId;
+ + UNIQUE_SYSTEM_ID_SEPARATOR
+ + originalId;
}
/**
@@ -485,7 +508,7 @@ import java.util.stream.Stream;
continue;
}
String id =
- asSystemRouteId(providerInfo.getUniqueId(), sourceRoute.getOriginalId());
+ asUniqueSystemId(providerInfo.getUniqueId(), sourceRoute.getOriginalId());
var newRouteBuilder = new MediaRoute2Info.Builder(id, sourceRoute);
if ((sourceRoute.getSupportedRoutingTypes()
& MediaRoute2Info.FLAG_ROUTING_TYPE_SYSTEM_AUDIO)
@@ -534,6 +557,9 @@ import java.util.stream.Stream;
RoutingSessionInfo translatedSession;
synchronized (mLock) {
mSessionRecord = systemMediaSessionRecord;
+ mSessionOriginalIdToSessionRecord.put(
+ systemMediaSessionRecord.mOriginalId,
+ systemMediaSessionRecord);
mPackageNameToSessionRecord.put(
mClientPackageName, systemMediaSessionRecord);
mPendingSessionCreations.remove(mRequestId);
@@ -576,24 +602,38 @@ import java.util.stream.Stream;
private final String mProviderId;
- @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
- @NonNull
- private RoutingSessionInfo mSourceSessionInfo;
+ /**
+ * The {@link RoutingSessionInfo#getOriginalId() original id} with which this session is
+ * published.
+ *
+ * <p>Derived from the service routing session, using {@link #asUniqueSystemId}.
+ */
+ private final String mOriginalId;
+
+ // @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ @NonNull private RoutingSessionInfo mSourceSessionInfo;
/**
- * The same as {@link #mSourceSessionInfo}, except ids are {@link #asSystemRouteId system
+ * The same as {@link #mSourceSessionInfo}, except ids are {@link #asUniqueSystemId system
* provider ids}.
*/
- @NonNull
- private RoutingSessionInfo mTranslatedSessionInfo;
+ // @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ @NonNull private RoutingSessionInfo mTranslatedSessionInfo;
SystemMediaSessionRecord(
@NonNull String providerId, @NonNull RoutingSessionInfo sessionInfo) {
mProviderId = providerId;
mSourceSessionInfo = sessionInfo;
+ mOriginalId =
+ asUniqueSystemId(sessionInfo.getProviderId(), sessionInfo.getOriginalId());
mTranslatedSessionInfo = asSystemProviderSession(sessionInfo);
}
+ // @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ public String getServiceSessionId() {
+ return mSourceSessionInfo.getOriginalId();
+ }
+
@Override
public void onSessionUpdate(@NonNull RoutingSessionInfo sessionInfo) {
RoutingSessionInfo translatedSessionInfo = asSystemProviderSession(sessionInfo);
@@ -612,31 +652,32 @@ import java.util.stream.Stream;
@Override
public void onSessionReleased() {
synchronized (mLock) {
- removeSelfFromSessionMap();
+ removeSelfFromSessionMaps();
}
notifyGlobalSessionInfoUpdated();
}
- @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ // @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
@Nullable
public ProviderProxyRecord getProxyRecord() {
ProviderProxyRecord provider = mProxyRecords.get(mProviderId);
if (provider == null) {
// Unexpected condition where the proxy is no longer available while there's an
// ongoing session. Could happen due to a crash in the provider process.
- removeSelfFromSessionMap();
+ removeSelfFromSessionMaps();
}
return provider;
}
- @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
- private void removeSelfFromSessionMap() {
+ // @GuardedBy("SystemMediaRoute2Provider2.this.mLock")
+ private void removeSelfFromSessionMaps() {
+ mSessionOriginalIdToSessionRecord.remove(mOriginalId);
mPackageNameToSessionRecord.remove(mSourceSessionInfo.getClientPackageName());
}
private RoutingSessionInfo asSystemProviderSession(RoutingSessionInfo session) {
var builder =
- new RoutingSessionInfo.Builder(session)
+ new RoutingSessionInfo.Builder(session, mOriginalId)
.setProviderId(mUniqueId)
.setSystemSession(true)
.clearSelectedRoutes()
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index e47f8ae9d3a5..02f817e9cd44 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -146,18 +146,27 @@ public class GroupHelper {
private static List<NotificationSectioner> NOTIFICATION_SHADE_SECTIONS =
getNotificationShadeSections();
+ private static List<NotificationSectioner> NOTIFICATION_BUNDLE_SECTIONS;
+
private static List<NotificationSectioner> getNotificationShadeSections() {
ArrayList<NotificationSectioner> sectionsList = new ArrayList<>();
if (android.service.notification.Flags.notificationClassification()) {
sectionsList.addAll(List.of(
new NotificationSectioner("PromotionsSection", 0, (record) ->
- NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())),
+ NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT),
new NotificationSectioner("SocialSection", 0, (record) ->
- NotificationChannel.SOCIAL_MEDIA_ID.equals(record.getChannel().getId())),
+ NotificationChannel.SOCIAL_MEDIA_ID.equals(record.getChannel().getId())
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT),
new NotificationSectioner("NewsSection", 0, (record) ->
- NotificationChannel.NEWS_ID.equals(record.getChannel().getId())),
+ NotificationChannel.NEWS_ID.equals(record.getChannel().getId())
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT),
new NotificationSectioner("RecsSection", 0, (record) ->
- NotificationChannel.RECS_ID.equals(record.getChannel().getId()))));
+ NotificationChannel.RECS_ID.equals(record.getChannel().getId())
+ && record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT)
+ ));
+
+ NOTIFICATION_BUNDLE_SECTIONS = new ArrayList<>(sectionsList);
}
if (Flags.notificationForceGroupConversations()) {
@@ -828,7 +837,7 @@ public class GroupHelper {
}
moveNotificationsToNewSection(record.getUserId(), pkgName,
List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)),
- REGROUP_REASON_BUNDLE);
+ Map.of(record.getKey(), REGROUP_REASON_BUNDLE));
return;
}
}
@@ -895,7 +904,12 @@ public class GroupHelper {
return false;
}
- return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId());
+ return isInBundleSection(record);
+ }
+
+ private static boolean isInBundleSection(final NotificationRecord record) {
+ final NotificationSectioner sectioner = getSection(record);
+ return (sectioner != null && NOTIFICATION_BUNDLE_SECTIONS.contains(sectioner));
}
/**
@@ -1084,10 +1098,10 @@ public class GroupHelper {
FullyQualifiedGroupKey newGroup) { }
/**
- * Called when a notification channel is updated (channel attributes have changed),
- * so that this helper can adjust the aggregate groups by moving children
- * if their section has changed.
- * see {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ * Called when a notification channel is updated (channel attributes have changed), so that this
+ * helper can adjust the aggregate groups by moving children if their section has changed. see
+ * {@link #onNotificationPostedWithDelay(NotificationRecord, List, Map)}
+ *
* @param userId the userId of the channel
* @param pkgName the channel's package
* @param channel the channel that was updated
@@ -1095,19 +1109,30 @@ public class GroupHelper {
*/
@FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING)
public void onChannelUpdated(final int userId, final String pkgName,
- final NotificationChannel channel, final List<NotificationRecord> notificationList) {
+ final NotificationChannel channel, final List<NotificationRecord> notificationList,
+ ArrayMap<String, NotificationRecord> summaryByGroupKey) {
synchronized (mAggregatedNotifications) {
+ final ArrayMap<String, Integer> regroupingReasonMap = new ArrayMap<>();
ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
for (NotificationRecord r : notificationList) {
if (r.getChannel().getId().equals(channel.getId())
&& r.getSbn().getPackageName().equals(pkgName)
&& r.getUserId() == userId) {
notificationsToCheck.put(r.getKey(), r);
+ regroupingReasonMap.put(r.getKey(), REGROUP_REASON_CHANNEL_UPDATE);
+ if (notificationRegroupOnClassification()) {
+ // Notification is unbundled and original summary found
+ // => regroup in original group
+ if (!isInBundleSection(r)
+ && isOriginalGroupSummaryPresent(r, summaryByGroupKey)) {
+ regroupingReasonMap.put(r.getKey(),
+ REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP);
+ }
+ }
}
}
- regroupNotifications(userId, pkgName, notificationsToCheck,
- REGROUP_REASON_CHANNEL_UPDATE);
+ regroupNotifications(userId, pkgName, notificationsToCheck, regroupingReasonMap);
}
}
@@ -1124,8 +1149,10 @@ public class GroupHelper {
synchronized (mAggregatedNotifications) {
ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
notificationsToCheck.put(record.getKey(), record);
+ ArrayMap<String, Integer> regroupReasons = new ArrayMap<>();
+ regroupReasons.put(record.getKey(), REGROUP_REASON_BUNDLE);
regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
- notificationsToCheck, REGROUP_REASON_BUNDLE);
+ notificationsToCheck, regroupReasons);
}
}
@@ -1144,16 +1171,16 @@ public class GroupHelper {
ArrayMap<String, NotificationRecord> notificationsToCheck = new ArrayMap<>();
notificationsToCheck.put(record.getKey(), record);
regroupNotifications(record.getUserId(), record.getSbn().getPackageName(),
- notificationsToCheck,
- originalSummaryExists ? REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP
- : REGROUP_REASON_UNBUNDLE);
+ notificationsToCheck, Map.of(record.getKey(),
+ originalSummaryExists ? REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP
+ : REGROUP_REASON_UNBUNDLE));
}
}
@GuardedBy("mAggregatedNotifications")
private void regroupNotifications(int userId, String pkgName,
ArrayMap<String, NotificationRecord> notificationsToCheck,
- @RegroupingReason int regroupingReason) {
+ Map<String, Integer> regroupReasons) {
// The list of notification operations required after the channel update
final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
@@ -1170,15 +1197,13 @@ public class GroupHelper {
// Handle "grouped correctly" notifications that were re-classified (bundled)
if (notificationRegroupOnClassification()) {
- if (regroupingReason == REGROUP_REASON_BUNDLE) {
- notificationsToMove.addAll(
- getReclassifiedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
- }
+ notificationsToMove.addAll(
+ getReclassifiedNotificationsMoveOps(userId, pkgName, notificationsToCheck));
}
// Batch move to new section
if (!notificationsToMove.isEmpty()) {
- moveNotificationsToNewSection(userId, pkgName, notificationsToMove, regroupingReason);
+ moveNotificationsToNewSection(userId, pkgName, notificationsToMove, regroupReasons);
}
}
@@ -1187,9 +1212,9 @@ public class GroupHelper {
final ArrayList<NotificationMoveOp> notificationsToMove = new ArrayList<>();
for (NotificationRecord record : notificationsToCheck.values()) {
if (isChildOfValidAppGroup(record)) {
- // Check if section changes
+ // Check if section changes to a bundle section
NotificationSectioner sectioner = getSection(record);
- if (sectioner != null) {
+ if (sectioner != null && NOTIFICATION_BUNDLE_SECTIONS.contains(sectioner)) {
FullyQualifiedGroupKey newFullAggregateGroupKey =
new FullyQualifiedGroupKey(userId, pkgName, sectioner);
if (DEBUG) {
@@ -1204,6 +1229,24 @@ public class GroupHelper {
return notificationsToMove;
}
+ /**
+ * Checks if the original group's summary exists for a notification that was regrouped
+ * @param r notification to check
+ * @param summaryByGroupKey map of the current group summaries
+ * @return true if the original group summary exists
+ */
+ public static boolean isOriginalGroupSummaryPresent(final NotificationRecord r,
+ final ArrayMap<String, NotificationRecord> summaryByGroupKey) {
+ if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
+ final String oldGroupKey = GroupHelper.getFullAggregateGroupKey(
+ r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId());
+ NotificationRecord groupSummary = summaryByGroupKey.get(oldGroupKey);
+ // We only care about app-provided valid groups
+ return (groupSummary != null && !GroupHelper.isAggregatedGroup(groupSummary));
+ }
+ return false;
+ }
+
@GuardedBy("mAggregatedNotifications")
private List<NotificationMoveOp> getAutogroupedNotificationsMoveOps(int userId, String pkgName,
ArrayMap<String, NotificationRecord> notificationsToCheck) {
@@ -1298,7 +1341,8 @@ public class GroupHelper {
@GuardedBy("mAggregatedNotifications")
private void moveNotificationsToNewSection(final int userId, final String pkgName,
- final List<NotificationMoveOp> notificationsToMove, int regroupingReason) {
+ final List<NotificationMoveOp> notificationsToMove,
+ final Map<String, Integer> regroupReasons) {
record GroupUpdateOp(FullyQualifiedGroupKey groupKey, NotificationRecord record,
boolean hasSummary) { }
// Bundled operations to apply to groups affected by the channel update
@@ -1317,7 +1361,7 @@ public class GroupHelper {
Log.i(TAG,
"moveNotificationToNewSection: " + record + " " + newFullAggregateGroupKey
+ " from: " + oldFullAggregateGroupKey + " regroupingReason: "
- + regroupingReason);
+ + regroupReasons);
}
// Update/remove aggregate summary for old group
@@ -1347,7 +1391,8 @@ public class GroupHelper {
// after all notifications have been handled
if (newFullAggregateGroupKey != null) {
if (notificationRegroupOnClassification()
- && regroupingReason == REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP) {
+ && regroupReasons.getOrDefault(record.getKey(), REGROUP_REASON_CHANNEL_UPDATE)
+ == REGROUP_REASON_UNBUNDLE_ORIGINAL_GROUP) {
// Just reset override group key, original summary exists
// => will be grouped back to its original group
record.setOverrideGroupKey(null);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6d565d2157e1..21ac05c24259 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -346,6 +346,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.NotificationChannelGroupsHelper;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
@@ -1929,27 +1930,17 @@ public class NotificationManagerService extends SystemService {
if (DBG) {
Slog.v(TAG, "unclassifyNotification: " + r);
}
-
- boolean hasOriginalSummary = false;
- if (r.getSbn().isAppGroup() && r.getNotification().isGroupChild()) {
- final String oldGroupKey = GroupHelper.getFullAggregateGroupKey(
- r.getSbn().getPackageName(), r.getOriginalGroupKey(), r.getUserId());
- NotificationRecord groupSummary = mSummaryByGroupKey.get(oldGroupKey);
- // We only care about app-provided valid groups
- hasOriginalSummary = (groupSummary != null
- && !GroupHelper.isAggregatedGroup(groupSummary));
- }
-
// Only NotificationRecord's mChannel is updated when bundled, the Notification
// mChannelId will always be the original channel.
String origChannelId = r.getNotification().getChannelId();
NotificationChannel originalChannel = mPreferencesHelper.getNotificationChannel(
r.getSbn().getPackageName(), r.getUid(), origChannelId, false);
String currChannelId = r.getChannel().getId();
- boolean isBundled = NotificationChannel.SYSTEM_RESERVED_IDS.contains(currChannelId);
- if (originalChannel != null && !origChannelId.equals(currChannelId) && isBundled) {
+ boolean isClassified = NotificationChannel.SYSTEM_RESERVED_IDS.contains(currChannelId);
+ if (originalChannel != null && !origChannelId.equals(currChannelId) && isClassified) {
r.updateNotificationChannel(originalChannel);
- mGroupHelper.onNotificationUnbundled(r, hasOriginalSummary);
+ mGroupHelper.onNotificationUnbundled(r,
+ GroupHelper.isOriginalGroupSummaryPresent(r, mSummaryByGroupKey));
}
}
@@ -2034,9 +2025,9 @@ public class NotificationManagerService extends SystemService {
Slog.v(TAG, "reclassifyNotification: " + r);
}
- boolean isBundled = NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ boolean isClassified = NotificationChannel.SYSTEM_RESERVED_IDS.contains(
r.getChannel().getId());
- if (r.getBundleType() != Adjustment.TYPE_OTHER && !isBundled) {
+ if (r.getBundleType() != Adjustment.TYPE_OTHER && !isClassified) {
final Bundle classifBundle = new Bundle();
classifBundle.putInt(KEY_TYPE, r.getBundleType());
Adjustment adj = new Adjustment(r.getSbn().getPackageName(), r.getKey(),
@@ -3583,7 +3574,7 @@ public class NotificationManagerService extends SystemService {
synchronized (mNotificationLock) {
mGroupHelper.onChannelUpdated(
UserHandle.getUserHandleForUid(uid).getIdentifier(), pkg,
- updatedChannel, mNotificationList);
+ updatedChannel, mNotificationList, mSummaryByGroupKey);
}
}, DELAY_FORCE_REGROUP_TIME);
}
@@ -5020,8 +5011,8 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
String pkg) {
checkCallerIsSystemOrSameApp(pkg);
- return mPreferencesHelper.getNotificationChannelGroups(
- pkg, Binder.getCallingUid(), false, false, true, true, null);
+ return mPreferencesHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid(),
+ NotificationChannelGroupsHelper.Params.forAllGroups());
}
@Override
@@ -5141,8 +5132,9 @@ public class NotificationManagerService extends SystemService {
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
String pkg, int uid, boolean includeDeleted) {
enforceSystemOrSystemUI("getNotificationChannelGroupsForPackage");
- return mPreferencesHelper.getNotificationChannelGroups(
- pkg, uid, includeDeleted, true, false, true, null);
+ return mPreferencesHelper.getNotificationChannelGroups(pkg, uid,
+ new NotificationChannelGroupsHelper.Params(includeDeleted, true, false, true,
+ null));
}
@Override
@@ -5170,8 +5162,9 @@ public class NotificationManagerService extends SystemService {
}
}
- return mPreferencesHelper.getNotificationChannelGroups(
- pkg, uid, false, true, false, true, recentlySentChannels);
+ return mPreferencesHelper.getNotificationChannelGroups(pkg, uid,
+ NotificationChannelGroupsHelper.Params.onlySpecifiedOrBlockedChannels(
+ recentlySentChannels));
}
@Override
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 7d45cd9752b8..33c94a7e63da 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -92,6 +92,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags;
import com.android.internal.logging.MetricsLogger;
+import com.android.internal.notification.NotificationChannelGroupsHelper;
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
@@ -1678,18 +1679,8 @@ public class PreferencesHelper implements RankingConfig {
if (r == null || groupId == null || !r.groups.containsKey(groupId)) {
return null;
}
- NotificationChannelGroup group = r.groups.get(groupId).clone();
- group.setChannels(new ArrayList<>());
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- if (includeDeleted || !nc.isDeleted()) {
- if (groupId.equals(nc.getGroup())) {
- group.addChannel(nc);
- }
- }
- }
- return group;
+ return NotificationChannelGroupsHelper.getGroupWithChannels(groupId,
+ r.channels.values(), r.groups, includeDeleted);
}
}
@@ -1706,51 +1697,16 @@ public class PreferencesHelper implements RankingConfig {
}
public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
- int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty,
- boolean includeBlocked, Set<String> activeChannelFilter) {
+ int uid, NotificationChannelGroupsHelper.Params params) {
Objects.requireNonNull(pkg);
- Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
synchronized (mLock) {
PackagePreferences r = getPackagePreferencesLocked(pkg, uid);
if (r == null) {
return ParceledListSlice.emptyList();
}
- NotificationChannelGroup nonGrouped = new NotificationChannelGroup(null, null);
- int N = r.channels.size();
- for (int i = 0; i < N; i++) {
- final NotificationChannel nc = r.channels.valueAt(i);
- boolean includeChannel = (includeDeleted || !nc.isDeleted())
- && (activeChannelFilter == null
- || (includeBlocked && nc.getImportance() == IMPORTANCE_NONE)
- || activeChannelFilter.contains(nc.getId()))
- && !SYSTEM_RESERVED_IDS.contains(nc.getId());
- if (includeChannel) {
- if (nc.getGroup() != null) {
- if (r.groups.get(nc.getGroup()) != null) {
- NotificationChannelGroup ncg = groups.get(nc.getGroup());
- if (ncg == null) {
- ncg = r.groups.get(nc.getGroup()).clone();
- ncg.setChannels(new ArrayList<>());
- groups.put(nc.getGroup(), ncg);
- }
- ncg.addChannel(nc);
- }
- } else {
- nonGrouped.addChannel(nc);
- }
- }
- }
- if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
- groups.put(null, nonGrouped);
- }
- if (includeEmpty) {
- for (NotificationChannelGroup group : r.groups.values()) {
- if (!groups.containsKey(group.getId())) {
- groups.put(group.getId(), group);
- }
- }
- }
- return new ParceledListSlice<>(new ArrayList<>(groups.values()));
+ return new ParceledListSlice<>(
+ NotificationChannelGroupsHelper.getGroupsWithChannels(r.channels.values(),
+ r.groups, params));
}
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 8710438d76b3..d6b587b5f16d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -251,14 +251,17 @@ public final class OverlayManagerService extends SystemService {
private final Object mLock = new Object();
+ @GuardedBy("mLock")
private final AtomicFile mSettingsFile;
private final PackageManagerHelperImpl mPackageManager;
private final UserManagerService mUserManager;
+ @GuardedBy("mLock")
private final OverlayManagerSettings mSettings;
+ @GuardedBy("mLock")
private final OverlayManagerServiceImpl mImpl;
private final OverlayActorEnforcer mActorEnforcer;
@@ -296,7 +299,9 @@ public final class OverlayManagerService extends SystemService {
UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
umi.addUserLifecycleListener(new UserLifecycleListener());
- restoreSettings();
+ // No async stuff happening in the constructor yet, so it's OK to call a ...Locked()
+ // method without a lock here.
+ restoreSettingsLocked();
// Wipe all shell overlays on boot, to recover from a potentially broken device
String shellPkgName = TextUtils.emptyIfNull(
@@ -403,6 +408,20 @@ public final class OverlayManagerService extends SystemService {
return userIds;
}
+ /**
+ * Ensure that the caller has permission to interact with the given userId.
+ * If the calling user is not the same as the provided user, the caller needs
+ * to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
+ * root).
+ *
+ * @param userId the user to interact with
+ * @param message message for any SecurityException
+ */
+ static int handleIncomingUser(final int userId, @NonNull final String message) {
+ return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, false, true, message, null);
+ }
+
private void handlePackageAdd(String packageName, Bundle extras, int userId) {
final boolean replacing = extras.getBoolean(Intent.EXTRA_REPLACING, false);
if (replacing) {
@@ -902,25 +921,28 @@ public final class OverlayManagerService extends SystemService {
throws RemoteException {
try {
traceBegin(TRACE_TAG_RRO, "OMS#commit " + transaction);
- try {
- executeAllRequests(transaction);
- } catch (Exception e) {
- final long ident = Binder.clearCallingIdentity();
+ synchronized (mLock) {
try {
- restoreSettings();
- } finally {
- Binder.restoreCallingIdentity(ident);
+ executeAllRequestsLocked(transaction);
+ } catch (Exception e) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ restoreSettingsLocked();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ Slog.d(TAG, "commit failed: " + e.getMessage(), e);
+ throw new SecurityException("commit failed"
+ + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
- Slog.d(TAG, "commit failed: " + e.getMessage(), e);
- throw new SecurityException("commit failed"
- + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
}
} finally {
traceEnd(TRACE_TAG_RRO);
}
}
- private Set<UserPackage> executeRequest(
+ @GuardedBy("mLock")
+ private Set<UserPackage> executeRequestLocked(
@NonNull final OverlayManagerTransaction.Request request)
throws OperationFailedException {
Objects.requireNonNull(request, "Transaction contains a null request");
@@ -995,33 +1017,29 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void executeAllRequests(@NonNull final OverlayManagerTransaction transaction)
+ @GuardedBy("mLock")
+ private void executeAllRequestsLocked(@NonNull final OverlayManagerTransaction transaction)
throws OperationFailedException {
if (DEBUG) {
Slog.d(TAG, "commit " + transaction);
}
- if (transaction == null) {
- throw new IllegalArgumentException("null transaction");
- }
- synchronized (mLock) {
- // execute the requests (as calling user)
- Set<UserPackage> affectedPackagesToUpdate = null;
- for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
- Request request = it.next();
- affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
- executeRequest(request));
- }
+ // execute the requests (as calling user)
+ Set<UserPackage> affectedPackagesToUpdate = null;
+ for (Iterator<Request> it = transaction.getRequests(); it.hasNext(); ) {
+ Request request = it.next();
+ affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
+ executeRequestLocked(request));
+ }
- // past the point of no return: the entire transaction has been
- // processed successfully, we can no longer fail: continue as
- // system_server
- final long ident = Binder.clearCallingIdentity();
- try {
- updateTargetPackagesLocked(affectedPackagesToUpdate);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ // past the point of no return: the entire transaction has been
+ // processed successfully, we can no longer fail: continue as
+ // system_server
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ updateTargetPackagesLocked(affectedPackagesToUpdate);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
}
}
@@ -1037,7 +1055,7 @@ public final class OverlayManagerService extends SystemService {
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
final DumpState dumpState = new DumpState();
- dumpState.setUserId(UserHandle.USER_ALL);
+ int userId = UserHandle.USER_ALL;
int opti = 0;
while (opti < args.length) {
@@ -1064,9 +1082,7 @@ public final class OverlayManagerService extends SystemService {
return;
}
try {
- final int userId = UserHandle.parseUserArg(args[opti]);
- final int realUserId = handleIncomingUser(userId, "dump");
- dumpState.setUserId(realUserId);
+ userId = UserHandle.parseUserArg(args[opti]);
opti++;
} catch (Exception e) {
pw.println("Error: " + e.getMessage());
@@ -1105,6 +1121,9 @@ public final class OverlayManagerService extends SystemService {
}
enforceDumpPermission("dump");
+ final int realUserId = userId != UserHandle.USER_ALL
+ ? handleIncomingUser(userId, "dump") : userId;
+ dumpState.setUserId(realUserId);
synchronized (mLock) {
mImpl.dump(pw, dumpState);
if (dumpState.getPackageName() == null) {
@@ -1114,20 +1133,6 @@ public final class OverlayManagerService extends SystemService {
}
/**
- * Ensure that the caller has permission to interact with the given userId.
- * If the calling user is not the same as the provided user, the caller needs
- * to hold the INTERACT_ACROSS_USERS_FULL permission (or be system uid or
- * root).
- *
- * @param userId the user to interact with
- * @param message message for any SecurityException
- */
- private int handleIncomingUser(final int userId, @NonNull final String message) {
- return ActivityManager.handleIncomingUser(Binder.getCallingPid(),
- Binder.getCallingUid(), userId, false, true, message, null);
- }
-
- /**
* Enforce that the caller holds the DUMP permission (or is system or root).
*
* @param message used as message if SecurityException is thrown
@@ -1447,12 +1452,14 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) {
if (updatedTarget != null) {
updateTargetPackagesLocked(Set.of(updatedTarget));
}
}
+ @GuardedBy("mLock")
private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) {
if (CollectionUtils.isEmpty(updatedTargets)) {
return;
@@ -1548,6 +1555,7 @@ public final class OverlayManagerService extends SystemService {
}
@NonNull
+ @GuardedBy("mLock")
private SparseArray<List<String>> updatePackageManagerLocked(
@Nullable Set<UserPackage> targets) {
if (CollectionUtils.isEmpty(targets)) {
@@ -1568,6 +1576,7 @@ public final class OverlayManagerService extends SystemService {
* targetPackageNames: the target themselves and shared libraries)
*/
@NonNull
+ @GuardedBy("mLock")
private List<String> updatePackageManagerLocked(@NonNull Collection<String> targetPackageNames,
final int userId) {
try {
@@ -1623,6 +1632,7 @@ public final class OverlayManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
private void persistSettingsLocked() {
if (DEBUG) {
Slog.d(TAG, "Writing overlay settings");
@@ -1638,35 +1648,35 @@ public final class OverlayManagerService extends SystemService {
}
}
- private void restoreSettings() {
+ @GuardedBy("mLock")
+ private void restoreSettingsLocked() {
try {
traceBegin(TRACE_TAG_RRO, "OMS#restoreSettings");
- synchronized (mLock) {
- if (!mSettingsFile.getBaseFile().exists()) {
- return;
- }
- try (FileInputStream stream = mSettingsFile.openRead()) {
- mSettings.restore(stream);
- // We might have data for dying users if the device was
- // restarted before we received USER_REMOVED. Remove data for
- // users that will not exist after the system is ready.
+ if (!mSettingsFile.getBaseFile().exists()) {
+ return;
+ }
+ try (FileInputStream stream = mSettingsFile.openRead()) {
+ mSettings.restore(stream);
- final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
- final int[] liveUserIds = new int[liveUsers.size()];
- for (int i = 0; i < liveUsers.size(); i++) {
- liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
- }
- Arrays.sort(liveUserIds);
+ // We might have data for dying users if the device was
+ // restarted before we received USER_REMOVED. Remove data for
+ // users that will not exist after the system is ready.
- for (int userId : mSettings.getUsers()) {
- if (Arrays.binarySearch(liveUserIds, userId) < 0) {
- mSettings.removeUser(userId);
- }
+ final List<UserInfo> liveUsers = mUserManager.getUsers(true /*excludeDying*/);
+ final int[] liveUserIds = new int[liveUsers.size()];
+ for (int i = 0; i < liveUsers.size(); i++) {
+ liveUserIds[i] = liveUsers.get(i).getUserHandle().getIdentifier();
+ }
+ Arrays.sort(liveUserIds);
+
+ for (int userId : mSettings.getUsers()) {
+ if (Arrays.binarySearch(liveUserIds, userId) < 0) {
+ mSettings.removeUser(userId);
}
- } catch (IOException | XmlPullParserException e) {
- Slog.e(TAG, "failed to restore overlay state", e);
}
+ } catch (IOException | XmlPullParserException e) {
+ Slog.e(TAG, "failed to restore overlay state", e);
}
} finally {
traceEnd(TRACE_TAG_RRO);
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 0e9ec4d71421..02c0190224d0 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -715,20 +715,11 @@ final class OverlayManagerServiceImpl {
}
void dump(@NonNull final PrintWriter pw, @NonNull DumpState dumpState) {
- Pair<OverlayIdentifier, String> overlayIdmap = null;
- if (dumpState.getPackageName() != null) {
- OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(),
- dumpState.getOverlayName());
- OverlayInfo oi = mSettings.getNullableOverlayInfo(id, USER_SYSTEM);
- if (oi != null) {
- overlayIdmap = new Pair<>(id, oi.baseCodePath);
- }
- }
-
// settings
mSettings.dump(pw, dumpState);
// idmap data
+ final var overlayIdmap = mSettings.getIdentifierAndBaseCodePath(dumpState);
if (dumpState.getField() == null) {
Set<Pair<OverlayIdentifier, String>> allIdmaps = (overlayIdmap != null)
? Set.of(overlayIdmap) : mSettings.getAllIdentifiersAndBaseCodePaths();
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index f9758fcd5d01..e6b1c5f640f2 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -212,17 +212,41 @@ final class OverlayManagerSettings {
}
Set<String> getAllBaseCodePaths() {
+ // Overlays installed for multiple users have the same code path, avoid duplicates with Set.
final Set<String> paths = new ArraySet<>();
mItems.forEach(item -> paths.add(item.mBaseCodePath));
return paths;
}
Set<Pair<OverlayIdentifier, String>> getAllIdentifiersAndBaseCodePaths() {
+ // Overlays installed for multiple users have the same code path, avoid duplicates with Set.
final Set<Pair<OverlayIdentifier, String>> set = new ArraySet<>();
- mItems.forEach(item -> set.add(new Pair(item.mOverlay, item.mBaseCodePath)));
+ mItems.forEach(item -> set.add(new Pair<>(item.mOverlay, item.mBaseCodePath)));
return set;
}
+ @Nullable
+ Pair<OverlayIdentifier, String> getIdentifierAndBaseCodePath(@NonNull DumpState dumpState) {
+ if (dumpState.getPackageName() == null) {
+ return null;
+ }
+ OverlayIdentifier id = new OverlayIdentifier(dumpState.getPackageName(),
+ dumpState.getOverlayName());
+ final int userId = dumpState.getUserId();
+ for (int i = 0; i < mItems.size(); i++) {
+ final var item = mItems.get(i);
+ if (userId != UserHandle.USER_ALL && userId != item.mUserId) {
+ continue;
+ }
+ if (!id.equals(item.mOverlay)) {
+ continue;
+ }
+ // Overlays installed for multiple users have the same code path, return first found.
+ return new Pair<>(id, item.mBaseCodePath);
+ }
+ return null;
+ }
+
@NonNull
List<OverlayInfo> removeIf(@NonNull final Predicate<OverlayInfo> predicate, final int userId) {
return removeIf(info -> (predicate.test(info) && info.userId == userId));
diff --git a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
index b7f21125148c..39a6b1895c3a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
+++ b/services/core/java/com/android/server/om/OverlayManagerShellCommand.java
@@ -17,6 +17,7 @@
package com.android.server.om;
import static com.android.internal.content.om.OverlayConfig.PARTITION_ORDER_FILE_PATH;
+import static com.android.server.om.OverlayManagerService.handleIncomingUser;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -145,7 +146,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
out.println(" Load a package and print the value of a given resource");
out.println(" applying the current configuration and enabled overlays.");
out.println(" For a more fine-grained alternative, use 'idmap2 lookup'.");
- out.println(" fabricate [--user USER_ID] [--target-name OVERLAYABLE] --target PACKAGE");
+ out.println(" fabricate [--target-name OVERLAYABLE] --target PACKAGE");
out.println(" --name NAME [--file FILE] ");
out.println(" PACKAGE:TYPE/NAME ENCODED-TYPE-ID|TYPE-NAME ENCODED-VALUE");
out.println(" Create an overlay from a single resource. Caller must be root. Example:");
@@ -160,7 +161,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
final PrintWriter out = getOutPrintWriter();
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -234,7 +235,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runEnableDisable(final boolean enable) throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -269,7 +270,6 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 1;
}
- int userId = UserHandle.USER_SYSTEM;
String targetPackage = "";
String targetOverlayable = "";
String name = "";
@@ -278,9 +278,6 @@ final class OverlayManagerShellCommand extends ShellCommand {
String config = null;
while ((opt = getNextOption()) != null) {
switch (opt) {
- case "--user":
- userId = UserHandle.parseUserArg(getNextArgRequired());
- break;
case "--target":
targetPackage = getNextArgRequired();
break;
@@ -442,7 +439,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runEnableExclusive() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
boolean inCategory = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -469,7 +466,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
private int runSetPriority() throws RemoteException {
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
String opt;
while ((opt = getNextOption()) != null) {
switch (opt) {
@@ -498,7 +495,7 @@ final class OverlayManagerShellCommand extends ShellCommand {
final PrintWriter out = getOutPrintWriter();
final PrintWriter err = getErrPrintWriter();
- int userId = UserHandle.USER_SYSTEM;
+ int userId = UserHandle.USER_CURRENT;
boolean verbose = false;
String opt;
while ((opt = getNextOption()) != null) {
@@ -525,15 +522,16 @@ final class OverlayManagerShellCommand extends ShellCommand {
return 1;
}
+ final int realUserId = handleIncomingUser(userId, "runLookup");
final Resources res;
try {
res = mContext
- .createContextAsUser(UserHandle.of(userId), /* flags */ 0)
+ .createContextAsUser(UserHandle.of(realUserId), /* flags */ 0)
.getPackageManager()
.getResourcesForApplication(packageToLoad);
} catch (PackageManager.NameNotFoundException e) {
err.println(String.format("Error: failed to get resources for package %s for user %d",
- packageToLoad, userId));
+ packageToLoad, realUserId));
return 1;
}
final AssetManager assets = res.getAssets();
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index c62aaebf673b..4d97a83fc6b4 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -23,7 +23,9 @@ import android.content.Context;
import android.content.pm.LauncherUserInfo;
import android.content.pm.UserInfo;
import android.content.pm.UserProperties;
+import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.multiuser.Flags;
import android.os.Bundle;
import android.os.UserManager;
import android.util.DebugUtils;
@@ -617,4 +619,14 @@ public abstract class UserManagerInternal {
* if there is no such user.
*/
public abstract @UserIdInt int getCommunalProfileId();
+
+ /**
+ * 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)
+ && UserManager.supportsMultipleUsers();
+ }
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 8cbccf5feead..0a90c3644c2f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -33,7 +33,6 @@ 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;
@@ -60,7 +59,6 @@ import android.annotation.RequiresPermission;
import android.annotation.SpecialUsers.CanBeALL;
import android.annotation.SpecialUsers.CanBeCURRENT;
import android.annotation.SpecialUsers.CanBeNULL;
-import android.annotation.SpecialUsers.CannotBeSpecialUser;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -1161,7 +1159,7 @@ public class UserManagerService extends IUserManager.Stub {
showHsumNotificationIfNeeded();
- if (shouldShowNotificationForBackgroundUserSounds()) {
+ if (UserManagerInternal.shouldShowNotificationForBackgroundUserSounds()) {
new BackgroundUserSoundNotifier(mContext);
}
}
@@ -8499,16 +8497,6 @@ public class UserManagerService extends IUserManager.Stub {
}
/**
- * 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/slice/SlicePermissionManager.java b/services/core/java/com/android/server/slice/SlicePermissionManager.java
index 343d2e353abb..d118eaea37d9 100644
--- a/services/core/java/com/android/server/slice/SlicePermissionManager.java
+++ b/services/core/java/com/android/server/slice/SlicePermissionManager.java
@@ -29,6 +29,7 @@ import android.util.Log;
import android.util.Slog;
import android.util.Xml.Encoding;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.server.slice.SliceProviderPermissions.SliceAuthority;
@@ -76,8 +77,11 @@ public class SlicePermissionManager implements DirtyTracker {
private final File mSliceDir;
private final Context mContext;
private final Handler mHandler;
+ @GuardedBy("itself")
private final ArrayMap<PkgUser, SliceProviderPermissions> mCachedProviders = new ArrayMap<>();
+ @GuardedBy("itself")
private final ArrayMap<PkgUser, SliceClientPermissions> mCachedClients = new ArrayMap<>();
+ @GuardedBy("this")
private final ArraySet<Persistable> mDirty = new ArraySet<>();
@VisibleForTesting
@@ -354,14 +358,22 @@ public class SlicePermissionManager implements DirtyTracker {
// use addPersistableDirty(); this is just for tests
@VisibleForTesting
void addDirtyImmediate(Persistable obj) {
- mDirty.add(obj);
+ synchronized (this) {
+ mDirty.add(obj);
+ }
}
private void handleRemove(PkgUser pkgUser) {
getFile(SliceClientPermissions.getFileName(pkgUser)).delete();
getFile(SliceProviderPermissions.getFileName(pkgUser)).delete();
- mDirty.remove(mCachedClients.remove(pkgUser));
- mDirty.remove(mCachedProviders.remove(pkgUser));
+ synchronized (this) {
+ synchronized (mCachedClients) {
+ mDirty.remove(mCachedClients.remove(pkgUser));
+ }
+ synchronized (mCachedProviders) {
+ mDirty.remove(mCachedProviders.remove(pkgUser));
+ }
+ }
}
private final class H extends Handler {
@@ -379,7 +391,9 @@ public class SlicePermissionManager implements DirtyTracker {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD_DIRTY:
- mDirty.add((Persistable) msg.obj);
+ synchronized (SlicePermissionManager.this) {
+ mDirty.add((Persistable) msg.obj);
+ }
break;
case MSG_PERSIST:
handlePersist();
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index a5805043ac42..804cf4663bfd 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -79,7 +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.pm.UserManagerInternal;
import com.android.server.vibrator.VibrationSession.CallerInfo;
import com.android.server.vibrator.VibrationSession.DebugInfo;
import com.android.server.vibrator.VibrationSession.Status;
@@ -201,7 +201,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
VibratorManagerService.this::shouldCancelOnScreenOffLocked,
Status.CANCELLED_BY_SCREEN_OFF);
}
- } else if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()
+ } else if (UserManagerInternal.shouldShowNotificationForBackgroundUserSounds()
&& intent.getAction().equals(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND)) {
synchronized (mLock) {
maybeClearCurrentAndNextSessionsLocked(
@@ -325,7 +325,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
- if (UserManagerService.shouldShowNotificationForBackgroundUserSounds()) {
+ if (UserManagerInternal.shouldShowNotificationForBackgroundUserSounds()) {
filter.addAction(BackgroundUserSoundNotifier.ACTION_MUTE_SOUND);
}
context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 09b10739d469..d9c79b5c40bb 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -781,13 +781,71 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLastWallpaper == null || mFallbackWallpaper == null) return;
final WallpaperConnection systemConnection = mLastWallpaper.connection;
final WallpaperConnection fallbackConnection = mFallbackWallpaper.connection;
+ final WallpaperConnection lockConnection;
+ if (mLastLockWallpaper != null) {
+ lockConnection = mLastLockWallpaper.connection;
+ } else {
+ lockConnection = null;
+ }
if (fallbackConnection == null) {
Slog.w(TAG, "Fallback wallpaper connection has not been created yet!!");
return;
}
- // TODO(b/384520326) Passing DEFAULT_DISPLAY temporarily before we revamp the
- // multi-display supports.
- if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) {
+
+ if (enableConnectedDisplaysWallpaper()) {
+ mWallpaperDisplayHelper.forEachDisplayData(displayData -> {
+ int displayId = displayData.mDisplayId;
+ // If the display is already connected to the desired wallpaper(s), either the
+ // same wallpaper for both lock and system, or different wallpapers for each,
+ // any existing fallback wallpaper connection will be removed.
+ if (systemConnection.containsDisplay(displayId)
+ && (lockConnection == null || lockConnection.containsDisplay(displayId))) {
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.mDisplayConnector.get(displayId);
+ if (fallbackConnector != null && fallbackConnector.mEngine != null) {
+ fallbackConnector.disconnectLocked(mFallbackWallpaper.connection);
+ mFallbackWallpaper.connection.mDisplayConnector.remove(displayId);
+ }
+ return;
+ }
+
+ // Identify if the fallback wallpaper should be use for lock or system or both.
+ int which = 0;
+ if (!systemConnection.containsDisplay(displayId)) {
+ which |= FLAG_SYSTEM;
+ }
+ if (lockConnection == null || !lockConnection.containsDisplay(displayId)) {
+ which |= FLAG_LOCK;
+ }
+ if (mFallbackWallpaper.connection.containsDisplay(displayId)) {
+ // For existing fallback wallpaper connection, update the `which` flags.
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.mDisplayConnector.get(displayId);
+ try {
+ if (fallbackConnector != null && fallbackConnector.mEngine != null
+ && fallbackConnector.mWhich != which) {
+ fallbackConnector.mEngine.setWallpaperFlags(which);
+ mWindowManagerInternal.setWallpaperShowWhenLocked(
+ fallbackConnector.mToken,
+ /* showWhenLocked= */ (which & FLAG_LOCK) != 0);
+ fallbackConnector.mWhich = which;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to update fallback wallpaper engine flags", e);
+ }
+ } else {
+ // For new fallback connection, establish the connection with the desired
+ // `which` flag.
+ DisplayConnector fallbackConnector =
+ mFallbackWallpaper.connection.getDisplayConnectorOrCreate(displayId);
+ if (fallbackConnector != null && fallbackConnector.mEngine != null) {
+ fallbackConnector.mWhich = which;
+ fallbackConnector.connectLocked(mFallbackWallpaper.connection,
+ mFallbackWallpaper);
+ }
+ }
+ });
+ } else if (isWallpaperCompatibleForDisplay(DEFAULT_DISPLAY, systemConnection)) {
if (fallbackConnection.mDisplayConnector.size() != 0) {
fallbackConnection.forEachDisplayConnector(connector -> {
if (connector.mEngine != null) {
@@ -820,8 +878,13 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
boolean mDimensionsChanged;
boolean mPaddingChanged;
- DisplayConnector(int displayId) {
+ // This field is added for the fallback wallpaper, which may have a different which flag for
+ // a different display.
+ int mWhich;
+
+ DisplayConnector(int displayId, int which) {
mDisplayId = displayId;
+ mWhich = which;
}
void ensureStatusHandled() {
@@ -850,13 +913,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.w(TAG, "WallpaperService is not connected yet");
return;
}
+ int which = wallpaper.mWhich;
+ if (enableConnectedDisplaysWallpaper()) {
+ which = mWhich;
+ }
TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG);
t.traceBegin("WPMS.connectLocked-" + wallpaper.getComponent());
if (DEBUG) Slog.v(TAG, "Adding window token: " + mToken);
mWindowManagerInternal.addWindowToken(mToken, TYPE_WALLPAPER, mDisplayId,
null /* options */);
mWindowManagerInternal.setWallpaperShowWhenLocked(
- mToken, (wallpaper.mWhich & FLAG_LOCK) != 0);
+ mToken, (which & FLAG_LOCK) != 0);
if (multiCrop() && mImageWallpaper.equals(wallpaper.getComponent())) {
mWindowManagerInternal.setWallpaperCropHints(mToken,
mWallpaperCropper.getRelativeCropHints(wallpaper));
@@ -868,16 +935,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
try {
if (liveWallpaperContentHandling()) {
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo,
- wallpaper.getDescription());
+ wpdData.mWidth, wpdData.mHeight, wpdData.mPadding, mDisplayId, which,
+ connection.mInfo, wallpaper.getDescription());
} else {
WallpaperDescription desc = new WallpaperDescription.Builder().setComponent(
(connection.mInfo != null) ? connection.mInfo.getComponent()
: null).build();
connection.mService.attach(connection, mToken, TYPE_WALLPAPER, false,
- wpdData.mWidth, wpdData.mHeight,
- wpdData.mPadding, mDisplayId, wallpaper.mWhich, connection.mInfo, desc);
+ wpdData.mWidth, wpdData.mHeight, wpdData.mPadding, mDisplayId, which,
+ connection.mInfo, desc);
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed attaching wallpaper on display", e);
@@ -980,7 +1046,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final int displayId = display.getDisplayId();
final DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
- mDisplayConnector.append(displayId, new DisplayConnector(displayId));
+ mDisplayConnector.append(displayId,
+ new DisplayConnector(displayId, mWallpaper.mWhich));
}
}
}
@@ -1006,7 +1073,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
DisplayConnector connector = mDisplayConnector.get(displayId);
if (connector == null) {
if (mWallpaperDisplayHelper.isUsableDisplay(displayId, mClientUid)) {
- connector = new DisplayConnector(displayId);
+ connector = new DisplayConnector(displayId, mWallpaper.mWhich);
mDisplayConnector.append(displayId, connector);
}
}
@@ -1364,6 +1431,17 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
Slog.v(TAG, "static system+lock to system failure");
}
WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
+ // In the constructor, we copied the system+lock wallpaper to
+ // mOriginalSystem. However, the copied WallpaperData#connection is a
+ // reference, not a deep copy. This means
+ // currentSystem.connection.mWallpaper points to mOriginalSystem, so
+ // changes to currentSystem.mWhich alone won't update the corresponding
+ // flag in currentSystem.connection.mWallpaper.mWhich. Let's point
+ // currentSystem.connection.mWallpaper back to currentSystem.
+ if (enableConnectedDisplaysWallpaper()
+ && currentSystem.connection != null) {
+ currentSystem.connection.mWallpaper = currentSystem;
+ }
currentSystem.mWhich = FLAG_SYSTEM | FLAG_LOCK;
updateEngineFlags(currentSystem);
mLockWallpaperMap.remove(mNewWallpaper.userId);
@@ -1385,6 +1463,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
WallpaperData currentSystem = mWallpaperMap.get(mNewWallpaper.userId);
if (currentSystem.wallpaperId == mOriginalSystem.wallpaperId) {
+ // Fixing the reference, see above for more details.
+ if (enableConnectedDisplaysWallpaper()
+ && currentSystem.connection != null) {
+ currentSystem.connection.mWallpaper = currentSystem;
+ }
currentSystem.mWhich = FLAG_SYSTEM;
updateEngineFlags(currentSystem);
}
@@ -3814,6 +3897,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
wallpaper.connection.forEachDisplayConnector(
connector -> {
try {
+ connector.mWhich = wallpaper.mWhich;
if (connector.mEngine != null) {
connector.mEngine.setWallpaperFlags(wallpaper.mWhich);
mWindowManagerInternal.setWallpaperShowWhenLocked(
@@ -3949,8 +4033,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (mLastWallpaper == null) {
return;
}
+ int useFallbackWallpaperWhich = 0;
if (enableConnectedDisplaysWallpaper()) {
- int useFallbackWallpaperWhich = 0;
List<WallpaperData> wallpapers = new ArrayList<>();
wallpapers.add(mLastWallpaper);
// If the system and the lock wallpapers are not the same, we should also
@@ -3981,7 +4065,6 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
|| mFallbackWallpaper.connection == null) {
return;
}
- mFallbackWallpaper.mWhich = useFallbackWallpaperWhich;
} else {
if (isWallpaperCompatibleForDisplay(displayId, mLastWallpaper.connection)) {
final DisplayConnector connector =
@@ -3999,6 +4082,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final DisplayConnector connector = mFallbackWallpaper
.connection.getDisplayConnectorOrCreate(displayId);
if (connector == null) return;
+ connector.mWhich = useFallbackWallpaperWhich;
connector.connectLocked(mFallbackWallpaper.connection, mFallbackWallpaper);
} else {
Slog.w(TAG, "No wallpaper can be added to the new display");
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 89b46bc4eba4..e8498bca1809 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -178,6 +178,7 @@ import static com.android.server.wm.ActivityRecordProto.PROC_ID;
import static com.android.server.wm.ActivityRecordProto.PROVIDES_MAX_BOUNDS;
import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
+import static com.android.server.wm.ActivityRecordProto.REQUEST_OPEN_IN_BROWSER_EDUCATION_TIMESTAMP;
import static com.android.server.wm.ActivityRecordProto.SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS;
import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
@@ -9961,6 +9962,8 @@ final class ActivityRecord extends WindowToken {
aspectRatioOverrides.shouldEnableUserAspectRatioSettings());
proto.write(IS_USER_FULLSCREEN_OVERRIDE_ENABLED,
aspectRatioOverrides.isUserFullscreenOverrideEnabled());
+ proto.write(REQUEST_OPEN_IN_BROWSER_EDUCATION_TIMESTAMP,
+ mRequestOpenInBrowserEducationTimestamp);
}
@Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 906befc1edcc..0d88a9b1f8ee 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@ import android.util.SparseIntArray;
import android.view.Display;
import android.webkit.URLUtil;
import android.window.ActivityWindowInfo;
+import android.window.DesktopModeFlags;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
@@ -161,7 +162,6 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.pm.SaferIntentUtils;
import com.android.server.utils.Slogf;
import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
-import com.android.window.flags.Flags;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -2974,7 +2974,8 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
@Override
public void accept(ActivityRecord r) {
- if (Flags.enableDesktopWindowingAppToWeb() && mInfo.capturedLink == null) {
+ if (DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_APP_TO_WEB.isTrue()
+ && mInfo.capturedLink == null) {
setCapturedLink(r);
}
if (r.mLaunchCookie != null) {
diff --git a/services/core/java/com/android/server/wm/DesktopModeHelper.java b/services/core/java/com/android/server/wm/DesktopModeHelper.java
index e3906f9119c2..0eea30a29580 100644
--- a/services/core/java/com/android/server/wm/DesktopModeHelper.java
+++ b/services/core/java/com/android/server/wm/DesktopModeHelper.java
@@ -52,8 +52,7 @@ public final class DesktopModeHelper {
* Return {@code true} if the current device supports desktop mode.
*/
// TODO(b/337819319): use a companion object instead.
- @VisibleForTesting
- static boolean isDesktopModeSupported(@NonNull Context context) {
+ private static boolean isDesktopModeSupported(@NonNull Context context) {
return context.getResources().getBoolean(R.bool.config_isDesktopModeSupported);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index a0d2d260b39e..ecf2787a2080 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5165,7 +5165,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Creates a LayerCaptureArgs object to represent the entire DisplayContent
*/
- LayerCaptureArgs getLayerCaptureArgs(Set<Integer> windowTypesToExclude) {
+ LayerCaptureArgs getLayerCaptureArgs(@Nullable ToBooleanFunction<WindowState> predicate) {
if (!mWmService.mPolicy.isScreenOn()) {
if (DEBUG_SCREENSHOT) {
Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
@@ -5178,17 +5178,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
LayerCaptureArgs.Builder builder = new LayerCaptureArgs.Builder(getSurfaceControl())
.setSourceCrop(mTmpRect);
- if (!windowTypesToExclude.isEmpty()) {
- ArrayList<SurfaceControl> surfaceControls = new ArrayList<>();
+ if (predicate != null) {
+ ArrayList<SurfaceControl> excludeLayers = new ArrayList<>();
forAllWindows(
window -> {
- if (windowTypesToExclude.contains(window.getWindowType())) {
- surfaceControls.add(window.mSurfaceControl);
+ if (!predicate.apply(window)) {
+ excludeLayers.add(window.mSurfaceControl);
}
- }, true
- );
- if (!surfaceControls.isEmpty()) {
- builder.setExcludeLayers(surfaceControls.toArray(new SurfaceControl[0]));
+ }, true);
+ if (!excludeLayers.isEmpty()) {
+ builder.setExcludeLayers(excludeLayers.toArray(new SurfaceControl[0]));
}
}
return builder.build();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 59ca79c7ffc3..cf16204f93a1 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -182,12 +182,18 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
if (control != null && control.getLeash() != null) {
ImeTracker.Token statsToken = getAndClearStatsToken();
if (statsToken == null) {
- ProtoLog.d(WM_DEBUG_IME, "IME getControl without statsToken");
- } else {
- ImeTracker.forLogging().onProgress(statsToken,
- ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
- control.setImeStatsToken(statsToken);
+ ProtoLog.w(WM_DEBUG_IME,
+ "IME getControl without statsToken (check previous request!). "
+ + "Start new request");
+ // TODO(b/353463205) remove this later after fixing the race of two requests
+ // that cancel each other (cf. b/383466954#comment19).
+ statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
+ ImeTracker.ORIGIN_SERVER, SoftInputShowHideReason.CONTROLS_CHANGED,
+ false /* fromUser */);
}
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_WM_GET_CONTROL_WITH_LEASH);
+ control.setImeStatsToken(statsToken);
}
}
return control;
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index cf464c707ff4..1fe6ad68a313 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1948,7 +1948,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final IntArray rootTaskIdsToRestore = mUserVisibleRootTasks.get(userId);
boolean homeInFront = false;
if (Flags.enableTopVisibleRootTaskPerUserTracking()) {
- if (rootTaskIdsToRestore == null) {
+ if (rootTaskIdsToRestore == null || rootTaskIdsToRestore.size() == 0) {
// If there are no root tasks saved, try restore id 0 which should create and launch
// the home task.
handleRootTaskLaunchOnUserSwitch(/* restoreRootTaskId */INVALID_TASK_ID);
@@ -1958,11 +1958,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
handleRootTaskLaunchOnUserSwitch(rootTaskIdsToRestore.get(i));
}
// Check if the top task is type home
- if (rootTaskIdsToRestore.size() > 0) {
- final int topRootTaskId = rootTaskIdsToRestore.get(
- rootTaskIdsToRestore.size() - 1);
- homeInFront = isHomeTask(topRootTaskId);
- }
+ final int topRootTaskId = rootTaskIdsToRestore.get(rootTaskIdsToRestore.size() - 1);
+ homeInFront = isHomeTask(topRootTaskId);
}
} else {
handleRootTaskLaunchOnUserSwitch(restoreRootTaskId);
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index c77b1d9a7bcf..6e224f07fcdc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -1137,6 +1137,15 @@ public abstract class WindowManagerInternal {
* Returns an instance of {@link ScreenshotHardwareBuffer} containing the current
* screenshot.
*/
- public abstract ScreenshotHardwareBuffer takeAssistScreenshot(
- Set<Integer> windowTypesToExclude);
+ public abstract ScreenshotHardwareBuffer takeAssistScreenshot();
+
+ /**
+ * Returns an instance of {@link ScreenshotHardwareBuffer} containing the current
+ * screenshot, excluding layers that are not appropriate to pass to contextual search
+ * services - such as the cursor or any current contextual search window.
+ *
+ * @param uid the UID of the contextual search application. System alert windows belonging
+ * to this UID will be excluded from the screenshot.
+ */
+ public abstract ScreenshotHardwareBuffer takeContextualSearchScreenshot(int uid);
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3a1d652f82d4..7f1924005b2f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -325,6 +325,7 @@ import android.window.WindowContainerToken;
import android.window.WindowContextInfo;
import com.android.internal.R;
+import com.android.internal.util.ToBooleanFunction;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
@@ -4159,7 +4160,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Nullable
- private ScreenshotHardwareBuffer takeAssistScreenshot(Set<Integer> windowTypesToExclude) {
+ private ScreenshotHardwareBuffer takeAssistScreenshot(
+ @Nullable ToBooleanFunction<WindowState> predicate) {
if (!checkCallingPermission(READ_FRAME_BUFFER, "requestAssistScreenshot()")) {
throw new SecurityException("Requires READ_FRAME_BUFFER permission");
}
@@ -4174,7 +4176,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
captureArgs = null;
} else {
- captureArgs = displayContent.getLayerCaptureArgs(windowTypesToExclude);
+ captureArgs = displayContent.getLayerCaptureArgs(predicate);
}
}
@@ -4204,8 +4206,7 @@ public class WindowManagerService extends IWindowManager.Stub
*/
@Override
public boolean requestAssistScreenshot(final IAssistDataReceiver receiver) {
- final ScreenshotHardwareBuffer shb =
- takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
+ final ScreenshotHardwareBuffer shb = takeAssistScreenshot(/* predicate= */ null);
final Bitmap bm = shb != null ? shb.asBitmap() : null;
FgThread.getHandler().post(() -> {
try {
@@ -8618,9 +8619,27 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public ScreenshotHardwareBuffer takeAssistScreenshot(Set<Integer> windowTypesToExclude) {
+ public ScreenshotHardwareBuffer takeAssistScreenshot() {
// WMS.takeAssistScreenshot takes care of the locking.
- return WindowManagerService.this.takeAssistScreenshot(windowTypesToExclude);
+ return WindowManagerService.this.takeAssistScreenshot(/* predicate */ null);
+ }
+
+ @Override
+ public ScreenshotHardwareBuffer takeContextualSearchScreenshot(int uid) {
+ // WMS.takeAssistScreenshot takes care of the locking.
+ return WindowManagerService.this.takeAssistScreenshot(win -> {
+ switch (win.getWindowType()) {
+ case LayoutParams.TYPE_STATUS_BAR:
+ case LayoutParams.TYPE_NAVIGATION_BAR:
+ case LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
+ case LayoutParams.TYPE_POINTER:
+ return false;
+ case LayoutParams.TYPE_APPLICATION_OVERLAY:
+ return uid != win.getOwningUid();
+ default:
+ return true;
+ }
+ });
}
}
diff --git a/services/supervision/java/com/android/server/supervision/SupervisionService.java b/services/supervision/java/com/android/server/supervision/SupervisionService.java
index ea85710eab44..a96c477c78d2 100644
--- a/services/supervision/java/com/android/server/supervision/SupervisionService.java
+++ b/services/supervision/java/com/android/server/supervision/SupervisionService.java
@@ -177,23 +177,24 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
}
- /** Ensures that supervision is enabled when the supervision app is the profile owner. */
+ /**
+ * Ensures that supervision is enabled when the supervision app is the profile owner.
+ *
+ * <p>The state syncing with the DevicePolicyManager can only enable supervision and never
+ * disable. Supervision can only be disabled explicitly via calls to the
+ * {@link #setSupervisionEnabledForUser} method.
+ */
private void syncStateWithDevicePolicyManager(@UserIdInt int userId) {
final DevicePolicyManagerInternal dpmInternal = mInjector.getDpmInternal();
final ComponentName po =
dpmInternal != null ? dpmInternal.getProfileOwnerAsUser(userId) : null;
if (po != null && po.getPackageName().equals(getSystemSupervisionPackage())) {
- setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
+ setSupervisionEnabledForUserInternal(userId, true, getSystemSupervisionPackage());
} else if (po != null && po.equals(getSupervisionProfileOwnerComponent())) {
// TODO(b/392071637): Consider not enabling supervision in case profile owner is given
// to the legacy supervision profile owner component.
setSupervisionEnabledForUserInternal(userId, true, po.getPackageName());
- } else {
- // TODO(b/381428475): Avoid disabling supervision when the app is not the profile owner.
- // This might only be possible after introducing specific and public APIs to enable
- // and disable supervision.
- setSupervisionEnabledForUserInternal(userId, false, /* supervisionAppPackage= */ null);
}
}
@@ -279,7 +280,7 @@ public class SupervisionService extends ISupervisionManager.Stub {
}
@VisibleForTesting
- @SuppressLint("MissingPermission") // not needed for a service
+ @SuppressLint("MissingPermission") // not needed for a system service
void registerProfileOwnerListener() {
IntentFilter poIntentFilter = new IntentFilter();
poIntentFilter.addAction(DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 8dc657ed75a6..b4e885fe5661 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -141,6 +141,7 @@ public final class UserManagerServiceTest {
.spyStatic(ActivityManager.class)
.mockStatic(Settings.Global.class)
.mockStatic(Settings.Secure.class)
+ .mockStatic(Resources.class)
.build();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
@@ -202,6 +203,7 @@ public final class UserManagerServiceTest {
doReturn(0)
.when(mSpyResources)
.getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+ doReturn(mSpyResources).when(() -> Resources.getSystem());
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
diff --git a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
index d80fd20dd1e6..ef77a0ee067f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/wallpaper/WallpaperManagerServiceTests.java
@@ -1028,6 +1028,154 @@ public class WallpaperManagerServiceTests {
}
// Verify a secondary display removes system decorations ended
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the new wallpaper is only compatible for display 0 and 3 but not
+ // 2.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 2 connections in mLastWallpaper and 1 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int incompatibleDisplayId = 2;
+ final int compatibleDisplayId = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
+ mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
+
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(incompatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the existing wallpaper is only compatible for display 0 and 3 but
+ // not 2.
+ // GIVEN the new wallpaper is compatible to 2.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 3 connections in mLastWallpaper and 0 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_displayBecomeCompatible_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int display2 = 2;
+ final int display3 = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
+ mService.removeWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ mService.addWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(3);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(display2)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(display3)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(0);
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the existing wallpaper is only compatible for display 0 and 3 but
+ // not 2.
+ // GIVEN the new wallpaper is incompatible to 2 and 3.
+ // WHEN the new wallpaper is set for system and lock via setWallpaperComponent.
+ // THEN there are 1 connections in mLastWallpaper and 2 connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_multiDisplays_displayBecomeIncompatible_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int display2 = 2;
+ final int display3 = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, display2, display3));
+ mService.removeWallpaperCompatibleDisplayForTest(display2);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ mService.removeWallpaperCompatibleDisplayForTest(display3);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM | FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(display2)).isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(display3)).isTrue();
+ assertThat(mService.mLastLockWallpaper).isNull();
+ }
+
+ // Test setWallpaperComponent on multiple displays.
+ // GIVEN 3 displays, 0, 2, 3, the new wallpaper is only compatible for display 0 and 3 but not
+ // 2.
+ // WHEN two different wallpapers set for system and lock via setWallpaperComponent.
+ // THEN there are two connections in mLastWallpaper, two connection in mLastLockWallpaper and
+ // one connection in mFallbackWallpaper.
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_WALLPAPER)
+ public void setWallpaperComponent_systemAndLockWallpapers_multiDisplays_shouldHaveExpectedConnections() {
+ // Skip if there is no pre-defined default wallpaper component.
+ assumeThat(sDefaultWallpaperComponent,
+ not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
+
+ final int testUserId = USER_SYSTEM;
+ mService.switchUser(testUserId, null);
+ final int incompatibleDisplayId = 2;
+ final int compatibleDisplayId = 3;
+ setUpDisplays(List.of(DEFAULT_DISPLAY, incompatibleDisplayId, compatibleDisplayId));
+ mService.removeWallpaperCompatibleDisplayForTest(incompatibleDisplayId);
+
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_SYSTEM, testUserId);
+ mService.setWallpaperComponent(sImageWallpaperComponentName, sContext.getOpPackageName(),
+ FLAG_LOCK, testUserId);
+
+ verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyLastLockWallpaperData(testUserId, sImageWallpaperComponentName);
+ verifyCurrentSystemData(testUserId);
+ assertThat(mService.mLastWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(DEFAULT_DISPLAY)).isTrue();
+ assertThat(mService.mLastWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper.connection.getConnectedEngineSize()).isEqualTo(2);
+ assertThat(mService.mLastLockWallpaper.connection.containsDisplay(DEFAULT_DISPLAY))
+ .isTrue();
+ assertThat(mService.mLastLockWallpaper.connection.containsDisplay(compatibleDisplayId))
+ .isTrue();
+ assertThat(mService.mFallbackWallpaper.connection.getConnectedEngineSize()).isEqualTo(1);
+ assertThat(mService.mFallbackWallpaper.connection.containsDisplay(incompatibleDisplayId))
+ .isTrue();
+ }
+
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
index f02bdae1d9e6..457fde8d74d0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/autoclick/AutoclickControllerTest.java
@@ -20,11 +20,11 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static com.android.server.testutils.MockitoUtilsKt.eq;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.testng.AssertJUnit.assertEquals;
-import static org.testng.AssertJUnit.assertNotNull;
-import static org.testng.AssertJUnit.assertNull;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
@@ -35,6 +35,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.InputDevice;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
@@ -82,69 +83,89 @@ public class AutoclickControllerTest {
@Test
public void onMotionEvent_lazyInitClickScheduler() {
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNotNull();
}
@Test
public void onMotionEvent_nonMouseSource_notInitClickScheduler() {
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
injectFakeNonMouseActionDownEvent();
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
}
@Test
public void onMotionEvent_lazyInitAutoclickSettingsObserver() {
- assertNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNotNull();
}
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorScheduler() {
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNotNull();
}
@Test
@DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOff_notInitAutoclickIndicatorScheduler() {
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
injectFakeMouseActionDownEvent();
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
}
@Test
@EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOn_lazyInitAutoclickIndicatorView() {
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNull();
injectFakeMouseActionDownEvent();
- assertNotNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNotNull();
}
@Test
@DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
public void onMotionEvent_flagOff_notInitAutoclickIndicatorView() {
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickIndicatorView).isNull();
+
+ injectFakeMouseActionDownEvent();
+
+ assertThat(mController.mAutoclickIndicatorView).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOn_lazyInitAutoclickTypePanelView() {
+ assertThat(mController.mAutoclickTypePanel).isNull();
+
+ injectFakeMouseActionDownEvent();
+
+ assertThat(mController.mAutoclickTypePanel).isNotNull();
+ }
+
+ @Test
+ @DisableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onMotionEvent_flagOff_notInitAutoclickTypePanelView() {
+ assertThat(mController.mAutoclickTypePanel).isNull();
injectFakeMouseActionDownEvent();
- assertNull(mController.mAutoclickIndicatorView);
+ assertThat(mController.mAutoclickTypePanel).isNull();
}
@Test
@@ -166,6 +187,18 @@ public class AutoclickControllerTest {
}
@Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onDestroy_flagOn_removeAutoclickTypePanelViewToWindowManager() {
+ injectFakeMouseActionDownEvent();
+ AutoclickTypePanel mockAutoclickTypePanel = mock(AutoclickTypePanel.class);
+ mController.mAutoclickTypePanel = mockAutoclickTypePanel;
+
+ mController.onDestroy();
+
+ verify(mockAutoclickTypePanel).hide();
+ }
+
+ @Test
public void onMotionEvent_initClickSchedulerDelayFromSetting() {
injectFakeMouseActionDownEvent();
@@ -175,7 +208,7 @@ public class AutoclickControllerTest {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT,
mTestableContext.getUserId());
- assertEquals(delay, mController.mClickScheduler.getDelayForTesting());
+ assertThat(mController.mClickScheduler.getDelayForTesting()).isEqualTo(delay);
}
@Test
@@ -189,7 +222,40 @@ public class AutoclickControllerTest {
Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
mTestableContext.getUserId());
- assertEquals(size, mController.mAutoclickIndicatorView.getRadiusForTesting());
+ assertThat(mController.mAutoclickIndicatorView.getRadiusForTesting()).isEqualTo(size);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_doNotUpdateMetaStateWhenControllerIsNull() {
+ assertThat(mController.mClickScheduler).isNull();
+
+ injectFakeKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.META_ALT_ON);
+
+ assertThat(mController.mClickScheduler).isNull();
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_updateMetaStateWhenControllerNotNull() {
+ injectFakeMouseActionDownEvent();
+
+ int metaState = KeyEvent.META_ALT_ON | KeyEvent.META_META_ON;
+ injectFakeKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, metaState);
+
+ assertThat(mController.mClickScheduler).isNotNull();
+ assertThat(mController.mClickScheduler.getMetaStateForTesting()).isEqualTo(metaState);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.accessibility.Flags.FLAG_ENABLE_AUTOCLICK_INDICATOR)
+ public void onKeyEvent_modifierKey_cancelAutoClickWhenAdditionalRegularKeyPresssed() {
+ injectFakeMouseActionDownEvent();
+
+ injectFakeKeyEvent(KeyEvent.KEYCODE_J, KeyEvent.META_ALT_ON);
+
+ assertThat(mController.mClickScheduler).isNotNull();
+ assertThat(mController.mClickScheduler.getMetaStateForTesting()).isEqualTo(0);
}
@Test
@@ -198,7 +264,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mClickScheduler);
+ assertThat(mController.mClickScheduler).isNull();
}
@Test
@@ -207,7 +273,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mAutoclickSettingsObserver);
+ assertThat(mController.mAutoclickSettingsObserver).isNull();
}
@Test
@@ -217,7 +283,7 @@ public class AutoclickControllerTest {
mController.onDestroy();
- assertNull(mController.mAutoclickIndicatorScheduler);
+ assertThat(mController.mAutoclickIndicatorScheduler).isNull();
}
private void injectFakeMouseActionDownEvent() {
@@ -232,6 +298,17 @@ public class AutoclickControllerTest {
mController.onMotionEvent(event, event, /* policyFlags= */ 0);
}
+ private void injectFakeKeyEvent(int keyCode, int modifiers) {
+ KeyEvent keyEvent = new KeyEvent(
+ /* downTime= */ 0,
+ /* eventTime= */ 0,
+ /* action= */ KeyEvent.ACTION_DOWN,
+ /* code= */ keyCode,
+ /* repeat= */ 0,
+ /* metaState= */ modifiers);
+ mController.onKeyEvent(keyEvent, /* policyFlags= */ 0);
+ }
+
private MotionEvent getFakeMotionDownEvent() {
return MotionEvent.obtain(
/* downTime= */ 0,
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 06958b81d846..1627f683cd3e 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -25,6 +25,7 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
+import static android.app.KeyguardManager.LOCK_ON_USER_SWITCH_CALLBACK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
@@ -115,7 +116,6 @@ import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.UserTypeDetails;
import com.android.server.pm.UserTypeFactory;
-import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerService;
import com.google.common.collect.Range;
@@ -1563,11 +1563,11 @@ public class UserControllerTest {
// and the thread is still alive
assertTrue(threadStartUser.isAlive());
- // mock send the keyguard shown event
- ArgumentCaptor<ActivityTaskManagerInternal.ScreenObserver> captor = ArgumentCaptor.forClass(
- ActivityTaskManagerInternal.ScreenObserver.class);
- verify(mInjector.mActivityTaskManagerInternal).registerScreenObserver(captor.capture());
- captor.getValue().onKeyguardStateChanged(true);
+ // mock the binder response for the user switch completion
+ ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+ verify(mInjector.mWindowManagerMock).lockNow(captor.capture());
+ IRemoteCallback.Stub.asInterface(captor.getValue().getBinder(
+ LOCK_ON_USER_SWITCH_CALLBACK)).sendResult(null);
// verify the switch now moves on...
Thread.sleep(1000);
@@ -1757,7 +1757,6 @@ public class UserControllerTest {
private final IStorageManager mStorageManagerMock;
private final UserManagerInternal mUserManagerInternalMock;
private final WindowManagerService mWindowManagerMock;
- private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
private final PowerManagerInternal mPowerManagerInternal;
private final AlarmManagerInternal mAlarmManagerInternal;
private final KeyguardManager mKeyguardManagerMock;
@@ -1779,7 +1778,6 @@ public class UserControllerTest {
mUserManagerMock = mock(UserManagerService.class);
mUserManagerInternalMock = mock(UserManagerInternal.class);
mWindowManagerMock = mock(WindowManagerService.class);
- mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
mStorageManagerMock = mock(IStorageManager.class);
mPowerManagerInternal = mock(PowerManagerInternal.class);
mAlarmManagerInternal = mock(AlarmManagerInternal.class);
@@ -1843,11 +1841,6 @@ public class UserControllerTest {
}
@Override
- ActivityTaskManagerInternal getActivityTaskManagerInternal() {
- return mActivityTaskManagerInternal;
- }
-
- @Override
PowerManagerInternal getPowerManagerInternal() {
return mPowerManagerInternal;
}
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
index 832bcd9d70e6..3caf7faa13ec 100644
--- a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -1 +1,2 @@
+# Bug component: 1345447
include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
index b150b1495042..da022780ea75 100644
--- a/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/supervision/SupervisionServiceTest.kt
@@ -161,6 +161,18 @@ class SupervisionServiceTest {
@Test
@RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
+ fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotDisableSupervision() {
+ service.mInternal.setSupervisionEnabledForUser(USER_ID, true)
+ whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
+ .thenReturn(ComponentName("other.package", "MainActivity"))
+
+ broadcastProfileOwnerChanged(USER_ID)
+
+ assertThat(service.isSupervisionEnabledForUser(USER_ID)).isTrue()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_ENABLE_SYNC_WITH_DPM)
fun profileOwnerChanged_supervisionAppIsNotProfileOwner_doesNotEnableSupervision() {
whenever(mockDpmInternal.getProfileOwnerAsUser(USER_ID))
.thenReturn(ComponentName("other.package", "MainActivity"))
@@ -258,7 +270,7 @@ class SupervisionServiceTest {
private companion object {
const val USER_ID = 100
- val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
+ const val APP_UID = USER_ID * UserHandle.PER_USER_RANGE
}
}
diff --git a/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
new file mode 100644
index 000000000000..5448a05aafc3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/zram/ZramMaintenanceTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.zram
+
+import android.app.job.JobInfo
+import android.app.job.JobParameters
+import android.app.job.JobScheduler
+import android.os.IMmd
+import android.os.PersistableBundle
+import android.os.RemoteException
+import android.testing.TestableContext
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.server.ZramMaintenance
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+private fun generateJobParameters(jobId: Int, extras: PersistableBundle): JobParameters {
+ return JobParameters(
+ null, "", jobId, extras, null, null, 0, false, false, false, null, null, null
+ )
+}
+
+@SmallTest
+@RunWith(JUnit4::class)
+class ZramMaintenanceTest {
+ private val context = TestableContext(InstrumentationRegistry.getInstrumentation().context)
+
+ @Captor
+ private lateinit var jobInfoCaptor: ArgumentCaptor<JobInfo>
+
+ @Mock
+ private lateinit var mockJobScheduler: JobScheduler
+
+ @Mock
+ private lateinit var mockMmd: IMmd
+
+ @Before
+ @Throws(RemoteException::class)
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.addMockSystemService(JobScheduler::class.java, mockJobScheduler)
+ }
+
+ @Test
+ fun startZramMaintenance() {
+ ZramMaintenance.startZramMaintenance(context)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val job = jobInfoCaptor.value
+ assertThat(job.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(job.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+
+ @Test
+ fun startJobForFirstTime() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(true)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobWithoutCheckStatus() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, false)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, never()).isZramMaintenanceSupported()
+ verify(mockMmd, times(1)).doZramMaintenanceAsync()
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isFalse()
+ }
+
+ @Test
+ fun startJobZramIsDisabled() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+ `when`(mockMmd.isZramMaintenanceSupported()).thenReturn(false)
+
+ ZramMaintenance.startJob(context, params, mockMmd)
+
+ verify(mockMmd, times(1)).isZramMaintenanceSupported()
+ verify(mockMmd, never()).doZramMaintenanceAsync()
+ verify(mockJobScheduler, never()).schedule(any())
+ }
+
+ @Test
+ fun startJobMmdIsNotReadyYet() {
+ val extras = PersistableBundle()
+ extras.putBoolean(ZramMaintenance.KEY_CHECK_STATUS, true)
+ val params = generateJobParameters(
+ ZramMaintenance.JOB_ID,
+ extras,
+ )
+
+ ZramMaintenance.startJob(context, params, null)
+
+ verify(mockJobScheduler, times(1)).schedule(jobInfoCaptor.capture())
+ val nextJob = jobInfoCaptor.value
+ assertThat(nextJob.id).isEqualTo(ZramMaintenance.JOB_ID)
+ assertThat(nextJob.extras.getBoolean(ZramMaintenance.KEY_CHECK_STATUS)).isTrue()
+ }
+} \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index fa733e85c89c..4a977be2aad9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -96,12 +96,12 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.ArrayList;
-import java.util.List;
-
import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
import platform.test.runner.parameterized.Parameters;
+import java.util.ArrayList;
+import java.util.List;
+
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
@RunWith(ParameterizedAndroidJunit4.class)
@@ -2671,7 +2671,7 @@ public class GroupHelperTest extends UiServiceTestCase {
r.updateNotificationChannel(channel);
}
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel,
- notificationList);
+ notificationList, summaryByGroup);
// Check that all notifications are moved to the silent section group
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
@@ -2735,7 +2735,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
}
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
- notificationList);
+ notificationList, summaryByGroup);
// Check that the override group key was cleared
for (NotificationRecord record: notificationList) {
@@ -2812,7 +2812,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
}
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
- notificationList);
+ notificationList, summaryByGroup);
// Check that the override group key was cleared
for (NotificationRecord record: notificationList) {
@@ -2864,7 +2864,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// Classify/bundle child notifications
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
@@ -2872,7 +2872,7 @@ public class GroupHelperTest extends UiServiceTestCase {
NotificationChannel.SOCIAL_MEDIA_ID);
final NotificationChannel newsChannel = new NotificationChannel(
NotificationChannel.NEWS_ID, NotificationChannel.NEWS_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_news = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "NewsSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_news = new NotificationAttributes(
@@ -2944,7 +2944,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// Classify/bundle child notifications
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
for (NotificationRecord record: notificationList) {
if (record.getChannel().getId().equals(channel1.getId())
&& record.getNotification().isGroupChild()) {
@@ -2999,7 +2999,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// Classify/bundle child notifications
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
@@ -3095,7 +3095,7 @@ public class GroupHelperTest extends UiServiceTestCase {
reset(mCallback);
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
@@ -3149,7 +3149,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// adjustments applied while enqueued will use NotificationAdjustmentExtractor.
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
@@ -3209,7 +3209,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// Classify/bundle all child notifications: original group & summary is removed
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
for (NotificationRecord record: notificationList) {
if (record.getOriginalGroupKey().contains("testGrp")
&& record.getNotification().isGroupChild()) {
@@ -3297,7 +3297,7 @@ public class GroupHelperTest extends UiServiceTestCase {
// Classify/bundle child notifications: all except one, to keep the original group
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
@@ -3378,6 +3378,314 @@ public class GroupHelperTest extends UiServiceTestCase {
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleByImportanceNotification_originalSummaryExists() {
+ // Check that unbundled notifications are moved to the original section and original group
+ // when the original summary is still present
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT + 1;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle child notifications: all except one, to keep the original group
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_LOW);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ int numChildrenBundled = 0;
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ numChildrenBundled++;
+ if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+ break;
+ }
+ }
+ }
+
+ // Check that 1 autogroup summaries were created for the social section
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Adjust group key for grouped notifications
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.setOverrideGroupKey(expectedGroupKey_social);
+ }
+ }
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications by changing the social channel importance to alerting
+ // => social section summary is destroyed
+ // and notifications are moved back to the original group
+ reset(mCallback);
+ socialChannel.setImportance(IMPORTANCE_DEFAULT);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, socialChannel,
+ notificationList, summaryByGroup);
+
+ // Check that the autogroup summary for the social section was removed
+ // and that no new autogroup summaries were created
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social));
+
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")) {
+ assertThat(record.getSbn().getOverrideGroupKey()).isNull();
+ assertThat(GroupHelper.getSection(record).mName).isEqualTo("AlertingSection");
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testUnbundleByImportanceNotification_originalSummaryRemoved() {
+ // Check that unbundled notifications are moved to the original section and autogrouped
+ // when the original summary is not present
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT + 1;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify/bundle child notifications: all except one, to keep the original group
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_LOW);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ int numChildrenBundled = 0;
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ numChildrenBundled++;
+ if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+ break;
+ }
+ }
+ }
+
+ // Check that 1 autogroup summaries were created for the social section
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+
+ // Adjust group key
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.setOverrideGroupKey(expectedGroupKey_social);
+ }
+ }
+
+ // Remove original summary
+ notificationList.remove(summary);
+ summaryByGroup.remove(summary.getGroupKey());
+
+ // Add 1 ungrouped notification in the original section
+ NotificationRecord ungroupedNotification = getNotificationRecord(pkg, 4242,
+ String.valueOf(4242), UserHandle.SYSTEM);
+ notificationList.add(ungroupedNotification);
+ mGroupHelper.onNotificationPosted(ungroupedNotification, false);
+
+ // Unbundle the bundled notifications by changing the social channel importance to alerting
+ // => social section summary is destroyed
+ // and notifications are moved back to the alerting section and autogrouped
+ reset(mCallback);
+ socialChannel.setImportance(IMPORTANCE_DEFAULT);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, socialChannel,
+ notificationList, summaryByGroup);
+
+ // Check that the autogroup summary for the social section was removed
+ // and that a new autogroup was created in the alerting section
+ final String expectedGroupKey_alerting = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "AlertingSection", UserHandle.SYSTEM.getIdentifier());
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_alerting), anyInt(), any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT + 1)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_alerting), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), eq(pkg),
+ eq(expectedGroupKey_social));
+ verify(mCallback, never()).removeAppProvidedSummaryOnClassification(anyString(),
+ anyString());
+ }
+
+ @Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_CLASSIFICATION,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION})
+ public void testClassifyWithAlertingImportance_doesNotBundle() {
+ // Check that classified notifications are autogrouped when channel importance
+ // is updated DEFAULT to LOW
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = AUTOGROUP_AT_COUNT + 1;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ final String originalAppGroupKey = summary.getGroupKey();
+ final NotificationChannel originalChannel = summary.getChannel();
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ mGroupHelper.onNotificationPostedWithDelay(child, notificationList, summaryByGroup);
+ }
+
+ // Classify child notifications to Alerting bundle channel => do not "bundle"
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ int numChildrenBundled = 0;
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ mGroupHelper.onChannelUpdated(record);
+ numChildrenBundled++;
+ if (numChildrenBundled == AUTOGROUP_AT_COUNT) {
+ break;
+ }
+ }
+ }
+
+ // Check that no autogroup summaries were created for the social section
+ verify(mCallback, never()).addAutoGroupSummary(anyInt(), anyString(), anyString(),
+ anyString(), anyInt(), any());
+ verify(mCallback, never()).addAutoGroup(anyString(), anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, never()).removeAppProvidedSummaryOnClassification(anyString(),
+ anyString());
+
+ // Change importance to LOW => autogroup notifications in bundle section
+ reset(mCallback);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ socialChannel.setImportance(IMPORTANCE_LOW);
+ for (NotificationRecord record: notificationList) {
+ if (record.getNotification().isGroupChild()
+ && record.getOriginalGroupKey().contains("testGrp")
+ && NotificationChannel.SYSTEM_RESERVED_IDS.contains(
+ record.getChannel().getId())) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+ mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, socialChannel,
+ notificationList, summaryByGroup);
+
+ // Check that 1 autogroup summaries were created for the social section
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString(),
+ eq(expectedGroupKey_social), eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyString(),
+ any());
+ verify(mCallback, times(AUTOGROUP_AT_COUNT)).removeAppProvidedSummaryOnClassification(
+ anyString(), eq(originalAppGroupKey));
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
final String pkg = "package";
@@ -3432,7 +3740,7 @@ public class GroupHelperTest extends UiServiceTestCase {
r.updateNotificationChannel(channel);
}
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel,
- notificationList);
+ notificationList, summaryByGroup);
// Check that all notifications are moved to the silent section group
verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
@@ -3489,7 +3797,7 @@ public class GroupHelperTest extends UiServiceTestCase {
}
}
mGroupHelper.onChannelUpdated(UserHandle.SYSTEM.getIdentifier(), pkg, channel1,
- notificationList);
+ notificationList, new ArrayMap<>());
// Check that channel1's notifications are moved to the silent section & autogroup all
NotificationAttributes expectedSummaryAttr = new NotificationAttributes(BASE_FLAGS,
@@ -3940,14 +4248,14 @@ public class GroupHelperTest extends UiServiceTestCase {
// Check that special categories are grouped in their own sections
final NotificationChannel promoChannel = new NotificationChannel(
NotificationChannel.PROMOTIONS_ID, NotificationChannel.PROMOTIONS_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final NotificationRecord notification_promotion = getNotificationRecord(mPkg, 0, "", mUser,
"", false, promoChannel);
assertThat(GroupHelper.getSection(notification_promotion).mName).isEqualTo(
"PromotionsSection");
final NotificationChannel newsChannel = new NotificationChannel(NotificationChannel.NEWS_ID,
- NotificationChannel.NEWS_ID, IMPORTANCE_DEFAULT);
+ NotificationChannel.NEWS_ID, IMPORTANCE_LOW);
final NotificationRecord notification_news = getNotificationRecord(mPkg, 0, "", mUser,
"", false, newsChannel);
assertThat(GroupHelper.getSection(notification_news).mName).isEqualTo(
@@ -3955,18 +4263,49 @@ public class GroupHelperTest extends UiServiceTestCase {
final NotificationChannel socialChannel = new NotificationChannel(
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
- IMPORTANCE_DEFAULT);
+ IMPORTANCE_LOW);
final NotificationRecord notification_social = getNotificationRecord(mPkg, 0, "", mUser,
"", false, socialChannel);
assertThat(GroupHelper.getSection(notification_social).mName).isEqualTo(
"SocialSection");
final NotificationChannel recsChannel = new NotificationChannel(NotificationChannel.RECS_ID,
- NotificationChannel.RECS_ID, IMPORTANCE_DEFAULT);
+ NotificationChannel.RECS_ID, IMPORTANCE_LOW);
final NotificationRecord notification_recs = getNotificationRecord(mPkg, 0, "", mUser,
"", false, recsChannel);
assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
"RecsSection");
+
+ // Check that bundle categories with importance > IMPORTANCE_LOW are grouped into Alerting
+ final NotificationChannel promoChannelAlerting = new NotificationChannel(
+ NotificationChannel.PROMOTIONS_ID, NotificationChannel.PROMOTIONS_ID,
+ IMPORTANCE_DEFAULT);
+ final NotificationRecord notification_promotion_alerting = getNotificationRecord(mPkg, 0,
+ "", mUser, "", false, promoChannelAlerting);
+ assertThat(GroupHelper.getSection(notification_promotion_alerting).mName).isEqualTo(
+ "AlertingSection");
+
+ final NotificationChannel newsChannelAlerting = new NotificationChannel(
+ NotificationChannel.NEWS_ID, NotificationChannel.NEWS_ID, IMPORTANCE_DEFAULT);
+ final NotificationRecord notification_news_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, newsChannelAlerting);
+ assertThat(GroupHelper.getSection(notification_news_alerting).mName).isEqualTo(
+ "AlertingSection");
+
+ final NotificationChannel socialChannelAlerting = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final NotificationRecord notification_social_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, socialChannelAlerting);
+ assertThat(GroupHelper.getSection(notification_social_alerting).mName).isEqualTo(
+ "AlertingSection");
+
+ final NotificationChannel recsChannelAlerting = new NotificationChannel(
+ NotificationChannel.RECS_ID, NotificationChannel.RECS_ID, IMPORTANCE_DEFAULT);
+ final NotificationRecord notification_recs_alerting = getNotificationRecord(mPkg, 0, "",
+ mUser, "", false, recsChannelAlerting);
+ assertThat(GroupHelper.getSection(notification_recs_alerting).mName).isEqualTo(
+ "AlertingSection");
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f8c8a1dbb481..3aa95449cc98 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -2884,7 +2884,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
waitForIdle();
verify(mGroupHelper, times(1)).onChannelUpdated(eq(Process.myUserHandle().getIdentifier()),
- eq(mPkg), eq(mTestNotificationChannel), any());
+ eq(mPkg), eq(mTestNotificationChannel), any(), any());
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 832ca51ae580..a9e8f4a0d965 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -158,6 +158,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
import com.android.internal.config.sysui.TestableFlagResolver;
+import com.android.internal.notification.NotificationChannelGroupsHelper;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.os.AtomsProto;
@@ -259,6 +260,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
public static List<FlagsParameterization> getParams() {
return FlagsParameterization.allCombinationsOf(
android.app.Flags.FLAG_API_RICH_ONGOING,
+ android.app.Flags.FLAG_UI_RICH_ONGOING,
FLAG_NOTIFICATION_CLASSIFICATION, FLAG_NOTIFICATION_CLASSIFICATION_UI,
FLAG_MODES_UI, android.app.Flags.FLAG_NM_BINDER_PERF_CACHE_CHANNELS);
}
@@ -661,6 +663,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
if (android.app.Flags.uiRichOngoing()) {
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, false, true);
mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true, true);
}
@@ -690,7 +693,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
List<NotificationChannelGroup> actualGroups = mXmlHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList();
+ PKG_N_MR1, UID_N_MR1,
+ NotificationChannelGroupsHelper.Params.forAllChannels(false)).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -774,7 +778,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3.getId(), false));
List<NotificationChannelGroup> actualGroups = mXmlHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, false, true, false, true, null).getList();
+ PKG_N_MR1, UID_N_MR1,
+ NotificationChannelGroupsHelper.Params.forAllChannels(false)).getList();
boolean foundNcg = false;
for (NotificationChannelGroup actual : actualGroups) {
if (ncg.getId().equals(actual.getId())) {
@@ -3426,8 +3431,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{
UID_N_MR1});
- assertEquals(0, mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList().size());
+ assertEquals(0, mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1,
+ NotificationChannelGroupsHelper.Params.forAllChannels(true)).getList().size());
}
@Test
@@ -3566,8 +3571,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel3, true, false,
UID_N_MR1, false);
- List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList();
+ List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(PKG_N_MR1,
+ UID_N_MR1, NotificationChannelGroupsHelper.Params.forAllChannels(true)).getList();
assertEquals(3, actual.size());
for (NotificationChannelGroup group : actual) {
if (group.getId() == null) {
@@ -3601,15 +3606,15 @@ public class PreferencesHelperTest extends UiServiceTestCase {
channel1.setGroup(ncg.getId());
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
UID_N_MR1, false);
- mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1, true, true, false, true, null)
- .getList();
+ mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1,
+ NotificationChannelGroupsHelper.Params.forAllChannels(true)).getList();
channel1.setImportance(IMPORTANCE_LOW);
mHelper.updateNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true,
UID_N_MR1, false);
- List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, true, true, false, true, null).getList();
+ List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(PKG_N_MR1,
+ UID_N_MR1, NotificationChannelGroupsHelper.Params.forAllChannels(true)).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
@@ -3634,8 +3639,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false,
UID_N_MR1, false);
- List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, false, false, true, true, null).getList();
+ List<NotificationChannelGroup> actual = mHelper.getNotificationChannelGroups(PKG_N_MR1,
+ UID_N_MR1, NotificationChannelGroupsHelper.Params.forAllGroups()).getList();
assertEquals(2, actual.size());
for (NotificationChannelGroup group : actual) {
@@ -6440,8 +6445,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
Set<String> filter = ImmutableSet.of("id3");
- NotificationChannelGroup actual = mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, false, true, false, true, filter).getList().get(0);
+ NotificationChannelGroup actual = mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1,
+ NotificationChannelGroupsHelper.Params.onlySpecifiedOrBlockedChannels(
+ filter)).getList().get(0);
assertEquals(2, actual.getChannels().size());
assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id3")).count());
assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count());
@@ -6468,8 +6474,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
Set<String> filter = ImmutableSet.of("id3");
- NotificationChannelGroup actual = mHelper.getNotificationChannelGroups(
- PKG_N_MR1, UID_N_MR1, false, true, false, false, filter).getList().get(0);
+ NotificationChannelGroup actual = mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1,
+ new NotificationChannelGroupsHelper.Params(false, true, false, false,
+ filter)).getList().get(0);
assertEquals(1, actual.getChannels().size());
assertEquals(1, actual.getChannels().stream().filter(c -> c.getId().equals("id3")).count());
assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count());
@@ -6638,6 +6645,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
public void testSetCanBePromoted_allowlistNotOverrideUser() {
+ // default value is true. So we need to set it false to trigger the change.
+ mHelper.setCanBePromoted(PKG_P, UID_P, false, true);
mHelper.setCanBePromoted(PKG_P, UID_P, true, true);
assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
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 767c02bd268f..b248218b6cef 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -109,7 +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.pm.UserManagerInternal;
import com.android.server.vibrator.VibrationSession.Status;
import org.junit.After;
@@ -898,7 +898,7 @@ public class VibratorManagerServiceTest {
@Test
public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable {
- assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
+ assumeTrue(UserManagerInternal.shouldShowNotificationForBackgroundUserSounds());
mockVibrators(1);
VibratorManagerService service = createSystemReadyService();
@@ -2760,7 +2760,7 @@ public class VibratorManagerServiceTest {
@Test
public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable {
- assumeTrue(UserManagerService.shouldShowNotificationForBackgroundUserSounds());
+ assumeTrue(UserManagerInternal.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/CameraCompatFreeformPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
index 716f86418bcb..560725241853 100644
--- a/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/CameraCompatFreeformPolicyTests.java
@@ -158,7 +158,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsCameraRunningAndWindowingModeEligible_notFreeformWindowing_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
}
@@ -169,7 +169,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsCameraRunningAndWindowingModeEligible_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.isCameraRunningAndWindowingModeEligible(mActivity));
}
@@ -179,7 +179,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_overrideDisabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -199,7 +199,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_notFreeformWindowing_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -210,7 +210,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testIsFreeformLetterboxingForCameraAllowed_optInFreeformCameraRunning_true() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.isFreeformLetterboxingForCameraAllowed(mActivity));
}
@@ -222,7 +222,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT, WINDOWING_MODE_FULLSCREEN);
doReturn(false).when(mActivity).inFreeformWindowingMode();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertNotInCameraCompatMode();
}
@@ -250,7 +250,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInPortrait_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_PORTRAIT);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -262,7 +263,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInLandscape_portraitCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_PORTRAIT_DEVICE_IN_LANDSCAPE);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -274,7 +276,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInPortrait_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_PORTRAIT);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -286,7 +289,8 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraConnected_deviceInLandscape_landscapeCameraCompatMode() throws Exception {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertInCameraCompatMode(CAMERA_COMPAT_FREEFORM_LANDSCAPE_DEVICE_IN_LANDSCAPE);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -299,12 +303,12 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
/* lastLetterbox= */ false);
assertActivityRefreshRequested(/* refreshRequested */ true);
- mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraClosed(CAMERA_ID_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// Activity is letterboxed from the previous configuration change.
callOnActivityConfigurationChanging(mActivity, /* letterboxNew= */ true,
/* lastLetterbox= */ true);
@@ -319,7 +323,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testCameraOpenedForDifferentPackage_notInCameraCompatMode() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
assertNotInCameraCompatMode();
}
@@ -329,7 +333,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testShouldApplyCameraCompatFreeformTreatment_overrideNotEnabled_returnsFalse() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.isTreatmentEnabledForActivity(mActivity,
/* checkOrientation */ true));
@@ -341,7 +345,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testShouldApplyCameraCompatFreeformTreatment_enabledByOverride_returnsTrue() {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mActivity.info
.isChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_FREEFORM_WINDOWING_TREATMENT));
@@ -356,7 +360,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
Configuration oldConfiguration = createConfiguration(/* letterbox= */ false);
Configuration newConfiguration = createConfiguration(/* letterbox= */ true);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -372,7 +376,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
oldConfiguration.windowConfiguration.setDisplayRotation(0);
newConfiguration.windowConfiguration.setDisplayRotation(90);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertTrue(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -388,7 +392,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
oldConfiguration.windowConfiguration.setDisplayRotation(0);
newConfiguration.windowConfiguration.setDisplayRotation(0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
assertFalse(mCameraCompatFreeformPolicy.shouldRefreshActivity(mActivity, newConfiguration,
oldConfiguration));
@@ -404,7 +408,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(false).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityForCameraCompat();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ false);
@@ -419,7 +423,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
@@ -434,7 +438,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.shouldRefreshActivityViaPauseForCameraCompat();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
@@ -446,7 +450,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
public void testGetCameraCompatAspectRatio_activityNotInCameraCompat_returnsDefaultAspRatio() {
configureActivity(SCREEN_ORIENTATION_FULL_USER);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
@@ -462,7 +466,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
final float configAspectRatio = 1.5f;
mWm.mAppCompatConfiguration.setCameraCompatAspectRatio(configAspectRatio);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(configAspectRatio,
@@ -480,7 +484,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
doReturn(true).when(mActivity.mAppCompatController.getCameraOverrides())
.isOverrideMinAspectRatioForCameraEnabled();
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
callOnActivityConfigurationChanging(mActivity);
assertEquals(MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO,
@@ -496,7 +500,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_PORTRAIT);
setDisplayRotation(ROTATION_270);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// This is a portrait rotation for a device with portrait natural orientation (most common,
// currently the only one supported).
@@ -511,7 +515,7 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
configureActivity(SCREEN_ORIENTATION_LANDSCAPE);
setDisplayRotation(ROTATION_0);
- mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+ onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
// This is a landscape rotation for a device with portrait natural orientation (most common,
// currently the only one supported).
@@ -616,6 +620,16 @@ public class CameraCompatFreeformPolicyTests extends WindowTestsBase {
.inFreeformWindowingMode();
}
+ private void onCameraOpened(@NonNull String cameraId, @NonNull String packageName) {
+ mCameraAvailabilityCallback.onCameraOpened(cameraId, packageName);
+ waitHandlerIdle(mDisplayContent.mWmService.mH);
+ }
+
+ private void onCameraClosed(@NonNull String cameraId) {
+ mCameraAvailabilityCallback.onCameraClosed(cameraId);
+ waitHandlerIdle(mDisplayContent.mWmService.mH);
+ }
+
private void assertInCameraCompatMode(@CameraCompatTaskInfo.FreeformCameraCompatMode int mode) {
assertEquals(mode, mCameraCompatFreeformPolicy.getCameraCompatMode(mActivity));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index cdb51fc1c645..fdde3b38f19f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -1345,7 +1345,7 @@ public class DesktopModeLaunchParamsModifierTests extends
private void setupDesktopModeLaunchParamsModifier(boolean isDesktopModeSupported,
boolean enforceDeviceRestrictions) {
doReturn(isDesktopModeSupported)
- .when(() -> DesktopModeHelper.isDesktopModeSupported(any()));
+ .when(() -> DesktopModeHelper.isDeviceEligibleForDesktopMode(any()));
doReturn(enforceDeviceRestrictions)
.when(DesktopModeHelper::shouldEnforceDeviceRestrictions);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 7e7a53148603..cd1c48554be9 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -2747,8 +2747,7 @@ public class VoiceInteractionManagerService extends SystemService {
isManagedProfileVisible = true;
}
}
- final ScreenCapture.ScreenshotHardwareBuffer shb =
- mWmInternal.takeAssistScreenshot(/* windowTypesToExclude= */ Set.of());
+ final ScreenCapture.ScreenshotHardwareBuffer shb = mWmInternal.takeAssistScreenshot();
final Bitmap bm = shb != null ? shb.asBitmap() : null;
// Now that everything is fetched, putting it in the launchIntent.
if (bm != null) {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 18f44ddff086..eebe49de0010 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -18,13 +18,12 @@ package com.android.server.wm.flicker
import android.app.Instrumentation
import android.content.Intent
-import android.os.UserHandle
import android.platform.test.annotations.Presubmit
-import android.provider.Settings
import android.tools.flicker.junit.FlickerBuilderProvider
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.traces.component.ComponentNameMatcher
+import android.tools.traces.executeShellCommand
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.tapl.LauncherInstrumentation
@@ -47,11 +46,8 @@ constructor(
) {
init {
tapl.setExpectedRotationCheckEnabled(true)
- Settings.System.putIntForUser(
- instrumentation.targetContext.contentResolver,
- Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY,
- 0,
- UserHandle.USER_CURRENT_OR_SELF
+ executeShellCommand(
+ "settings put system hide_rotation_lock_toggle_for_accessibility 1"
)
}
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 36db955c3085..37bdf6b8614d 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -1111,9 +1111,9 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "FULLSCREEN -> Maximizes a task to fit the screen",
+ "FULLSCREEN -> Turns a task into fullscreen",
intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
intArrayOf(KeyEvent.KEYCODE_FULLSCREEN),
0,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
diff --git a/tools/localedata/extract_icu_data.py b/tools/localedata/extract_icu_data.py
index ec531275af1c..899cd7f9ce5e 100755
--- a/tools/localedata/extract_icu_data.py
+++ b/tools/localedata/extract_icu_data.py
@@ -180,7 +180,14 @@ def pack_script_to_uint32(script):
def dump_representative_locales(representative_locales):
"""Dump the set of representative locales."""
- print()
+ print('''
+/*
+ * TODO: Consider turning the below switch statement into binary search
+ * to save the disk space when the table is larger in the future.
+ * Disassembled code shows that the jump table emitted by clang can be
+ * 4x larger than the data in disk size, but it depends on the optimization option.
+ * However, a switch statement will benefit from the future of compiler improvement.
+ */''')
print('bool isLocaleRepresentative(uint32_t language_and_region, const char* script) {')
print(' const uint64_t packed_locale =')
print(' ((static_cast<uint64_t>(language_and_region)) << 32u) |')