summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp22
-rw-r--r--apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java5
-rw-r--r--apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java7
-rw-r--r--apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java96
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java55
-rw-r--r--apex/jobscheduler/framework/aconfig/job.aconfig8
-rw-r--r--boot/preloaded-classes1
-rw-r--r--config/preloaded-classes1
-rw-r--r--core/api/current.txt3
-rw-r--r--core/api/system-current.txt18
-rw-r--r--core/api/test-current.txt2
-rw-r--r--core/java/android/accounts/AccountManager.java10
-rw-r--r--core/java/android/app/Activity.java14
-rw-r--r--core/java/android/app/ActivityThread.java2
-rw-r--r--core/java/android/app/Instrumentation.java42
-rw-r--r--core/java/android/app/Notification.java5
-rw-r--r--core/java/android/app/UiModeManager.java4
-rw-r--r--core/java/android/app/WallpaperManager.java2
-rw-r--r--core/java/android/app/assist/AssistContent.java71
-rw-r--r--core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig (renamed from core/java/android/companion/virtual/flags.aconfig)20
-rw-r--r--core/java/android/companion/virtual/flags/flags.aconfig18
-rw-r--r--core/java/android/companion/virtual/flags/launched_flags.aconfig6
-rw-r--r--core/java/android/content/Intent.java22
-rw-r--r--core/java/android/content/pm/RegisteredServicesCache.java69
-rw-r--r--core/java/android/content/pm/SystemFeaturesCache.aidl19
-rw-r--r--core/java/android/content/pm/SystemFeaturesCache.java133
-rw-r--r--core/java/android/content/pm/flags.aconfig8
-rw-r--r--core/java/android/content/res/ApkAssets.java56
-rw-r--r--core/java/android/content/res/ResourceTimer.java56
-rw-r--r--core/java/android/hardware/SystemSensorManager.java43
-rw-r--r--core/java/android/hardware/display/DisplayTopology.java3
-rw-r--r--core/java/android/hardware/input/InputSettings.java66
-rw-r--r--core/java/android/hardware/input/KeyGestureEvent.java4
-rw-r--r--core/java/android/os/BaseBundle.java9
-rw-r--r--core/java/android/os/GraphicsEnvironment.java49
-rw-r--r--core/java/android/provider/Settings.java31
-rw-r--r--core/java/android/text/Layout.java2
-rw-r--r--core/java/android/view/InputEventConsistencyVerifier.java2
-rw-r--r--core/java/android/view/InputWindowHandle.java2
-rw-r--r--core/java/android/view/SurfaceControl.java64
-rw-r--r--core/java/android/view/WindowManagerGlobal.java1
-rw-r--r--core/java/android/view/WindowManagerPolicyConstants.java3
-rw-r--r--core/java/android/view/accessibility/AccessibilityManager.java9
-rw-r--r--core/java/android/view/contentprotection/flags/content_protection_flags.aconfig7
-rw-r--r--core/java/android/window/WindowInfosListenerForTest.java8
-rw-r--r--core/java/android/window/flags/lse_desktop_experience.aconfig11
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig7
-rw-r--r--core/java/com/android/internal/accessibility/util/AccessibilityUtils.java21
-rw-r--r--core/java/com/android/internal/notification/SystemNotificationChannels.java8
-rw-r--r--core/java/com/android/internal/policy/IKeyguardService.aidl14
-rw-r--r--core/jni/android_content_res_ApkAssets.cpp66
-rw-r--r--core/jni/android_view_SurfaceControl.cpp20
-rw-r--r--core/proto/android/providers/settings/secure.proto2
-rw-r--r--core/proto/android/providers/settings/system.proto1
-rw-r--r--core/res/res/values/config.xml15
-rw-r--r--core/res/res/values/strings.xml17
-rw-r--r--core/res/res/values/symbols.xml12
-rw-r--r--core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java5
-rw-r--r--core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java116
-rw-r--r--core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java5
-rw-r--r--libs/WindowManager/Shell/Android.bp37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt229
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt534
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt33
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserver.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksTransitionObserverTest.kt2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java9
-rw-r--r--libs/androidfw/ApkAssets.cpp9
-rw-r--r--libs/androidfw/AssetsProvider.cpp82
-rw-r--r--libs/androidfw/Idmap.cpp21
-rw-r--r--libs/androidfw/ResourceTypes.cpp78
-rw-r--r--libs/androidfw/Util.cpp25
-rw-r--r--libs/androidfw/include/androidfw/ApkAssets.h2
-rw-r--r--libs/androidfw/include/androidfw/AssetsProvider.h25
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h32
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/androidfw/include/androidfw/misc.h6
-rw-r--r--libs/androidfw/misc.cpp69
-rw-r--r--libs/androidfw/tests/Idmap_test.cpp27
-rw-r--r--media/java/android/media/MediaCodec.java21
-rw-r--r--media/java/android/media/soundtrigger/SoundTriggerManager.java7
-rw-r--r--packages/CompanionDeviceManager/res/layout/activity_confirmation.xml4
-rw-r--r--packages/InputDevices/res/raw/keyboard_layout_romanian.kcm357
-rw-r--r--packages/InputDevices/res/values/strings.xml3
-rw-r--r--packages/InputDevices/res/xml/keyboard_layouts.xml7
-rw-r--r--packages/SettingsLib/DataStore/OWNERS1
-rw-r--r--packages/SettingsLib/Graph/OWNERS1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt1
-rw-r--r--packages/SettingsLib/Ipc/OWNERS1
-rw-r--r--packages/SettingsLib/Metadata/OWNERS1
-rw-r--r--packages/SettingsLib/OWNERS_catalyst9
-rw-r--r--packages/SettingsLib/Preference/OWNERS1
-rw-r--r--packages/SettingsLib/Service/OWNERS1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt83
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt178
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java17
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java36
-rw-r--r--packages/SettingsProvider/res/values/defaults.xml3
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java82
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java12
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java1
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java95
-rw-r--r--packages/SystemUI/Android.bp4
-rw-r--r--packages/SystemUI/OWNERS1
-rw-r--r--packages/SystemUI/aconfig/predictive_back.aconfig14
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig8
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt6
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt50
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt54
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt60
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt51
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt39
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt135
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt13
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt61
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt13
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt69
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt42
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayoutTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt)10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt56
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt213
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt46
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt63
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt206
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt22
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/call/ui/viewmodel/CallChipViewModelTest.kt78
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/SingleNotificationChipInteractorTest.kt51
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt82
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt116
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java85
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java280
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt91
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt111
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt23
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt117
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt73
-rw-r--r--packages/SystemUI/res/color/active_track_color.xml18
-rw-r--r--packages/SystemUI/res/color/inactive_track_color.xml19
-rw-r--r--packages/SystemUI/res/color/on_active_track_color.xml19
-rw-r--r--packages/SystemUI/res/color/on_inactive_track_color.xml19
-rw-r--r--packages/SystemUI/res/color/thumb_color.xml19
-rw-r--r--packages/SystemUI/res/layout/volume_dialog.xml2
-rw-r--r--packages/SystemUI/res/layout/volume_dialog_slider.xml5
-rw-r--r--packages/SystemUI/res/values/dimens.xml5
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/res/values/styles.xml12
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_constraint_set.xml5
-rw-r--r--packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml5
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt991
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt (renamed from packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt)0
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt124
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java234
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/shared/model/MediaControlChipModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java236
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java309
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java82
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt226
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt103
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt74
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt114
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt)43
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt819
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt116
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt63
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java197
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt)0
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt41
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt6
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt25
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt35
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt2
-rw-r--r--proto/src/system_messages.proto4
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java18
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickController.java70
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java17
-rw-r--r--services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java356
-rw-r--r--services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java2
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java5
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java4
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java2
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java6
-rw-r--r--services/core/Android.bp1
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java2
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java105
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java12
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java4
-rw-r--r--services/core/java/com/android/server/appop/AttributedOp.java11
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java387
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java106
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsRegistry.java298
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java689
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsTable.java128
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java220
-rw-r--r--services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java (renamed from services/core/java/com/android/server/appop/DiscreteRegistry.java)263
-rw-r--r--services/core/java/com/android/server/appop/HistoricalRegistry.java35
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java7
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateProvider.java5
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java8
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java7
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java30
-rw-r--r--services/core/java/com/android/server/input/InputSettingsObserver.java7
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java9
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java345
-rw-r--r--services/core/java/com/android/server/notification/ConditionProviders.java4
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java20
-rw-r--r--services/core/java/com/android/server/notification/ZenModeConditions.java9
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java47
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig12
-rw-r--r--services/core/java/com/android/server/om/IdmapDaemon.java61
-rw-r--r--services/core/java/com/android/server/om/OverlayActorEnforcer.java9
-rw-r--r--services/core/java/com/android/server/om/OverlayReferenceMapper.java62
-rw-r--r--services/core/java/com/android/server/pm/ResilientAtomicFile.java22
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackageItem.java2
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java10
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java2
-rw-r--r--services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java4
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java40
-rw-r--r--services/core/java/com/android/server/policy/TalkbackShortcutController.java32
-rw-r--r--services/core/java/com/android/server/policy/VoiceAccessShortcutController.java62
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java12
-rw-r--r--services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java9
-rw-r--r--services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java14
-rw-r--r--services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java1
-rw-r--r--services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java88
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java15
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java46
-rw-r--r--services/core/java/com/android/server/wm/AppCompatOverrides.java11
-rw-r--r--services/core/java/com/android/server/wm/AppCompatResizeOverrides.java38
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java5
-rw-r--r--services/core/java/com/android/server/wm/InputConfigAdapter.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java1
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp66
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java2
-rw-r--r--services/print/Android.bp13
-rw-r--r--services/print/java/com/android/server/print/RemotePrintService.java3
-rw-r--r--services/print/java/com/android/server/print/flags.aconfig9
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java30
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java34
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java235
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java187
-rw-r--r--services/tests/servicestests/src/com/android/server/accessibility/OWNERS2
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java309
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java (renamed from services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java)34
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java168
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java3
-rw-r--r--services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt66
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java7
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java217
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java21
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java245
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java23
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java34
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java28
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java16
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java258
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java107
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java41
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java105
-rw-r--r--services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java6
-rw-r--r--telephony/java/android/telephony/CellularIdentifierDisclosure.java20
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java3
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt14
-rw-r--r--tools/aapt2/cmd/Command.cpp169
-rw-r--r--tools/aapt2/cmd/Command.h54
-rw-r--r--tools/aapt2/cmd/Command_test.cpp41
398 files changed, 13061 insertions, 5514 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 214e0402c8fc..a60ced5835ea 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -65,13 +65,13 @@ aconfig_declarations_group {
"android.sdk.flags-aconfig-java",
"android.security.flags-aconfig-java",
"android.server.app.flags-aconfig-java",
+ "android.service.appprediction.flags-aconfig-java",
"android.service.autofill.flags-aconfig-java",
"android.service.chooser.flags-aconfig-java",
"android.service.compat.flags-aconfig-java",
"android.service.controls.flags-aconfig-java",
"android.service.dreams.flags-aconfig-java",
"android.service.notification.flags-aconfig-java",
- "android.service.appprediction.flags-aconfig-java",
"android.service.quickaccesswallet.flags-aconfig-java",
"android.service.voice.flags-aconfig-java",
"android.speech.flags-aconfig-java",
@@ -220,17 +220,6 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
-java_aconfig_library {
- name: "telephony_flags_core_java_exported_lib",
- aconfig_declarations: "telephony_flags",
- mode: "exported",
- min_sdk_version: "30",
- apex_available: [
- "com.android.wifi",
- ],
- defaults: ["framework-minus-apex-aconfig-java-defaults"],
-}
-
cc_aconfig_library {
name: "telephony_flags_c_lib",
aconfig_declarations: "telephony_flags",
@@ -534,7 +523,10 @@ aconfig_declarations {
package: "android.companion.virtualdevice.flags",
container: "system",
exportable: true,
- srcs: ["core/java/android/companion/virtual/flags/*.aconfig"],
+ srcs: [
+ "core/java/android/companion/virtual/flags/flags.aconfig",
+ "core/java/android/companion/virtual/flags/launched_flags.aconfig",
+ ],
}
java_aconfig_library {
@@ -559,7 +551,7 @@ aconfig_declarations {
name: "android.companion.virtual.flags-aconfig",
package: "android.companion.virtual.flags",
container: "system",
- srcs: ["core/java/android/companion/virtual/*.aconfig"],
+ srcs: ["core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig"],
}
// InputMethod
@@ -839,8 +831,8 @@ java_aconfig_library {
min_sdk_version: "30",
apex_available: [
"//apex_available:platform",
- "com.android.permission",
"com.android.nfcservices",
+ "com.android.permission",
],
}
diff --git a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
index df6e3c836256..e790874ebc61 100644
--- a/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
+++ b/apct-tests/perftests/aconfig/src/android/os/flagging/AconfigPackagePerfTest.java
@@ -43,7 +43,7 @@ public class AconfigPackagePerfTest {
@Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
- @Parameterized.Parameters(name = "isPlatform={0}")
+ @Parameterized.Parameters(name = "isPlatform_{0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] {{false}, {true}});
}
@@ -60,10 +60,9 @@ public class AconfigPackagePerfTest {
}
}
- @Parameterized.Parameter(0)
-
// if this variable is true, then the test query flags from system/product/vendor
// if this variable is false, then the test query flags from updatable partitions
+ @Parameterized.Parameter(0)
public boolean mIsPlatform;
@Test
diff --git a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java
index a12121fd13f7..5d39ccc882a8 100644
--- a/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java
+++ b/apct-tests/perftests/core/src/android/app/OverlayManagerPerfTest.java
@@ -20,7 +20,6 @@ import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.om.OverlayManager;
-import android.os.UserHandle;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.perftests.utils.TestPackageInstaller;
@@ -127,7 +126,7 @@ public class OverlayManagerPerfTest {
private void assertSetEnabled(boolean enabled, Context context, Stream<String> packagesStream) {
final var overlayPackages = packagesStream.toList();
overlayPackages.forEach(
- name -> sOverlayManager.setEnabled(name, enabled, UserHandle.SYSTEM));
+ name -> sOverlayManager.setEnabled(name, enabled, context.getUser()));
// Wait for the overlay changes to propagate
final var endTime = System.nanoTime() + TimeUnit.SECONDS.toNanos(20);
@@ -174,7 +173,7 @@ public class OverlayManagerPerfTest {
// Disable the overlay and remove the idmap for the next iteration of the test
state.pauseTiming();
assertSetEnabled(false, sContext, packageName);
- sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM);
+ sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser());
state.resumeTiming();
}
}
@@ -189,7 +188,7 @@ public class OverlayManagerPerfTest {
// Disable the overlay and remove the idmap for the next iteration of the test
state.pauseTiming();
assertSetEnabled(false, sContext, packageName);
- sOverlayManager.invalidateCachesForOverlay(packageName, UserHandle.SYSTEM);
+ sOverlayManager.invalidateCachesForOverlay(packageName, sContext.getUser());
state.resumeTiming();
}
}
diff --git a/apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java b/apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java
new file mode 100644
index 000000000000..43f545318124
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/content/pm/SystemFeaturesPerfTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.content.pm;
+
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.pm.RoSystemFeatures;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class SystemFeaturesPerfTest {
+ // As each query is relatively cheap, add an inner iteration loop to reduce execution noise.
+ private static final int NUM_ITERATIONS = 10;
+
+ @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Test
+ public void hasSystemFeature_PackageManager() {
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
+ pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+ pm.hasSystemFeature(PackageManager.FEATURE_IPSEC_TUNNELS);
+ pm.hasSystemFeature(PackageManager.FEATURE_AUTOFILL);
+ pm.hasSystemFeature("com.android.custom.feature.1");
+ pm.hasSystemFeature("foo");
+ pm.hasSystemFeature("");
+ }
+ }
+ }
+
+ @Test
+ public void hasSystemFeature_SystemFeaturesCache() {
+ final PackageManager pm =
+ InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
+ final SystemFeaturesCache cache =
+ new SystemFeaturesCache(Arrays.asList(pm.getSystemAvailableFeatures()));
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ cache.maybeHasFeature(PackageManager.FEATURE_WATCH, 0);
+ cache.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0);
+ cache.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0);
+ cache.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0);
+ cache.maybeHasFeature("com.android.custom.feature.1", 0);
+ cache.maybeHasFeature("foo", 0);
+ cache.maybeHasFeature("", 0);
+ }
+ }
+ }
+
+ @Test
+ public void hasSystemFeature_RoSystemFeatures() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ for (int i = 0; i < NUM_ITERATIONS; ++i) {
+ RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0);
+ RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_LEANBACK, 0);
+ RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_IPSEC_TUNNELS, 0);
+ RoSystemFeatures.maybeHasFeature(PackageManager.FEATURE_AUTOFILL, 0);
+ RoSystemFeatures.maybeHasFeature("com.android.custom.feature.1", 0);
+ RoSystemFeatures.maybeHasFeature("foo", 0);
+ RoSystemFeatures.maybeHasFeature("", 0);
+ }
+ }
+ }
+}
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
index 2d2cf1c80e1e..b04d08f6795f 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/InTaskTransitionTest.java
@@ -34,11 +34,20 @@ import android.view.WindowManagerGlobal;
import org.junit.Rule;
import org.junit.Test;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
/** Measure the performance of warm launch activity in the same task. */
public class InTaskTransitionTest extends WindowManagerPerfTestBase
implements RemoteCallback.OnResultListener {
private static final long TIMEOUT_MS = 5000;
+ private static final String LOG_SEPARATOR = "LOG_SEPARATOR";
@Rule
public final PerfManualStatusReporter mPerfStatusReporter = new PerfManualStatusReporter();
@@ -62,6 +71,7 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
final ManualBenchmarkState state = mPerfStatusReporter.getBenchmarkState();
long measuredTimeNs = 0;
+ long firstStartTime = 0;
boolean readerStarted = false;
while (state.keepRunning(measuredTimeNs)) {
@@ -70,6 +80,10 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
readerStarted = true;
}
final long startTime = SystemClock.elapsedRealtimeNanos();
+ if (readerStarted && firstStartTime == 0) {
+ firstStartTime = startTime;
+ executeShellCommand("log -t " + LOG_SEPARATOR + " " + firstStartTime);
+ }
activity.startActivity(next);
synchronized (mMetricsReader) {
try {
@@ -89,6 +103,7 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
state.addExtraResult("windowsDrawnDelayMs", metrics.mWindowsDrawnDelayMs);
}
}
+ addExtraTransitionInfo(firstStartTime, state);
}
@Override
@@ -99,6 +114,46 @@ public class InTaskTransitionTest extends WindowManagerPerfTestBase
}
}
+ private void addExtraTransitionInfo(long startTime, ManualBenchmarkState state) {
+ final ProcessBuilder pb = new ProcessBuilder("sh");
+ final String startLine = String.valueOf(startTime);
+ final String commitTimeStr = " commit=";
+ boolean foundStartLine = false;
+ try {
+ final Process process = pb.start();
+ final InputStream in = process.getInputStream();
+ final PrintWriter out = new PrintWriter(new BufferedWriter(
+ new OutputStreamWriter(process.getOutputStream())), true /* autoFlush */);
+ out.println("logcat -v brief -d *:S WindowManager:V " + LOG_SEPARATOR + ":I"
+ + " | grep -e 'Finish Transition' -e " + LOG_SEPARATOR);
+ out.println("exit");
+
+ String line;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
+ while ((line = reader.readLine()) != null) {
+ if (!foundStartLine) {
+ if (line.contains(startLine)) {
+ foundStartLine = true;
+ }
+ continue;
+ }
+ final int strPos = line.indexOf(commitTimeStr);
+ if (strPos < 0) {
+ continue;
+ }
+ final int endPos = line.indexOf("ms", strPos);
+ if (endPos > strPos) {
+ final int commitDelayMs = Math.round(Float.parseFloat(
+ line.substring(strPos + commitTimeStr.length(), endPos)));
+ state.addExtraResult("commitDelayMs", commitDelayMs);
+ }
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
/** The test activity runs on a different process to trigger metrics logs. */
public static class TestActivity extends Activity implements Runnable {
static final String CALLBACK = "callback";
diff --git a/apex/jobscheduler/framework/aconfig/job.aconfig b/apex/jobscheduler/framework/aconfig/job.aconfig
index 8b1a40c7f833..a0dfd1906938 100644
--- a/apex/jobscheduler/framework/aconfig/job.aconfig
+++ b/apex/jobscheduler/framework/aconfig/job.aconfig
@@ -17,14 +17,6 @@ flag {
}
flag {
- name: "backup_jobs_exemption"
- is_exported: true
- namespace: "backstage_power"
- description: "Introduce a new RUN_BACKUP_JOBS permission and exemption logic allowing for longer running jobs for apps whose primary purpose is to backup or sync content."
- bug: "318731461"
-}
-
-flag {
name: "handle_abandoned_jobs"
namespace: "backstage_power"
description: "Detect, report and take action on jobs that maybe abandoned by the app without calling jobFinished."
diff --git a/boot/preloaded-classes b/boot/preloaded-classes
index b83bd4e4d401..9926aef91ee1 100644
--- a/boot/preloaded-classes
+++ b/boot/preloaded-classes
@@ -6470,6 +6470,7 @@ android.os.connectivity.WifiActivityEnergyInfo
android.os.connectivity.WifiBatteryStats$1
android.os.connectivity.WifiBatteryStats
android.os.flagging.AconfigPackage
+android.os.flagging.PlatformAconfigPackage
android.os.health.HealthKeys$Constant
android.os.health.HealthKeys$Constants
android.os.health.HealthKeys$SortedIntArray
diff --git a/config/preloaded-classes b/config/preloaded-classes
index e53c78f65877..bdd95f8e67ae 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -6474,6 +6474,7 @@ android.os.connectivity.WifiActivityEnergyInfo
android.os.connectivity.WifiBatteryStats$1
android.os.connectivity.WifiBatteryStats
android.os.flagging.AconfigPackage
+android.os.flagging.PlatformAconfigPackage
android.os.health.HealthKeys$Constant
android.os.health.HealthKeys$Constants
android.os.health.HealthKeys$SortedIntArray
diff --git a/core/api/current.txt b/core/api/current.txt
index a87644741fe6..c4109392d6bd 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8947,18 +8947,19 @@ package android.app.assist {
method public android.content.ClipData getClipData();
method public android.os.Bundle getExtras();
method public android.content.Intent getIntent();
+ method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") @Nullable public android.net.Uri getSessionTransferUri();
method public String getStructuredData();
method public android.net.Uri getWebUri();
method public boolean isAppProvidedIntent();
method public boolean isAppProvidedWebUri();
method public void setClipData(android.content.ClipData);
method public void setIntent(android.content.Intent);
+ method @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public void setSessionTransferUri(@Nullable android.net.Uri);
method public void setStructuredData(String);
method public void setWebUri(android.net.Uri);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.assist.AssistContent> CREATOR;
field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXTRA_APP_FUNCTION_DATA = "android.app.assist.extra.APP_FUNCTION_DATA";
- field @FlaggedApi("com.android.window.flags.enable_desktop_windowing_app_to_web_education") public static final String EXTRA_SESSION_TRANSFER_WEB_URI = "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
}
public class AssistStructure implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index f82aecbd6d44..22af517900d2 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -581,7 +581,7 @@ package android.accessibilityservice {
package android.accounts {
public class AccountManager {
- method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>, @Nullable android.os.Handler);
+ method @FlaggedApi("android.app.admin.flags.split_create_managed_profile_enabled") @NonNull @RequiresPermission(anyOf={android.Manifest.permission.COPY_ACCOUNTS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public android.accounts.AccountManagerFuture<java.lang.Boolean> copyAccountToUser(@NonNull android.accounts.Account, @NonNull android.os.UserHandle, @NonNull android.os.UserHandle, @Nullable android.os.Handler, @Nullable android.accounts.AccountManagerCallback<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.accounts.AccountManagerFuture<android.os.Bundle> finishSessionAsUser(android.os.Bundle, android.app.Activity, android.os.UserHandle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
}
@@ -1290,7 +1290,6 @@ package android.app {
public class WallpaperManager {
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void clearWallpaper(int, int);
- method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
method @FlaggedApi("android.app.customization_packs_apis") public static int getOrientation(@NonNull android.graphics.Point);
method @FloatRange(from=0.0f, to=1.0f) @RequiresPermission(android.Manifest.permission.SET_WALLPAPER_DIM_AMOUNT) public float getWallpaperDimAmount();
method @FlaggedApi("android.app.customization_packs_apis") @Nullable public android.os.ParcelFileDescriptor getWallpaperFile(int, boolean);
@@ -8095,16 +8094,16 @@ package android.media.soundtrigger {
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void deleteModel(java.util.UUID);
method public int getDetectionServiceOperationsTimeout();
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getModelState(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int getModelState(@NonNull java.util.UUID);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean isRecognitionActive(@NonNull java.util.UUID);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public boolean isRecognitionActive(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int loadSoundModel(@NonNull android.hardware.soundtrigger.SoundTrigger.SoundModel);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int stopRecognition(@NonNull java.util.UUID);
- method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int unloadSoundModel(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int startRecognition(@NonNull java.util.UUID, @Nullable android.os.Bundle, @NonNull android.content.ComponentName, @NonNull android.hardware.soundtrigger.SoundTrigger.RecognitionConfig);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int stopRecognition(@NonNull java.util.UUID);
+ method @FlaggedApi("android.media.soundtrigger.manager_api") @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) @WorkerThread public int unloadSoundModel(@NonNull java.util.UUID);
method @Deprecated @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
}
@@ -15045,6 +15044,7 @@ package android.telephony {
method public int getCellularIdentifier();
method public int getNasProtocolMessage();
method @NonNull public String getPlmn();
+ method @FlaggedApi("com.android.internal.telephony.flags.vendor_specific_cellular_identifier_disclosure_indications") public boolean isBenign();
method public boolean isEmergency();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int CELLULAR_IDENTIFIER_IMEI = 2; // 0x2
@@ -15062,6 +15062,8 @@ package android.telephony {
field public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11; // 0xb
field public static final int NAS_PROTOCOL_MESSAGE_LOCATION_UPDATE_REQUEST = 5; // 0x5
field public static final int NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST = 7; // 0x7
+ field @FlaggedApi("com.android.internal.telephony.flags.vendor_specific_cellular_identifier_disclosure_indications") public static final int NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_FALSE = 12; // 0xc
+ field @FlaggedApi("com.android.internal.telephony.flags.vendor_specific_cellular_identifier_disclosure_indications") public static final int NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_TRUE = 13; // 0xd
field public static final int NAS_PROTOCOL_MESSAGE_TRACKING_AREA_UPDATE_REQUEST = 4; // 0x4
field public static final int NAS_PROTOCOL_MESSAGE_UNKNOWN = 0; // 0x0
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index cd2cc07b8cc3..a988acf1f4a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -536,6 +536,7 @@ package android.app {
method @Nullable public android.graphics.Bitmap getBitmap();
method @Nullable public android.graphics.Bitmap getBitmapAsUser(int, boolean, int);
method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull java.util.List<android.graphics.Point>, int, boolean);
+ method @FlaggedApi("android.app.customization_packs_apis") @NonNull @RequiresPermission(android.Manifest.permission.READ_WALLPAPER_INTERNAL) public android.util.SparseArray<android.graphics.Rect> getBitmapCrops(int);
method @FlaggedApi("com.android.window.flags.multi_crop") @NonNull public java.util.List<android.graphics.Rect> getBitmapCrops(@NonNull android.graphics.Point, @NonNull java.util.List<android.graphics.Point>, @Nullable java.util.Map<android.graphics.Point,android.graphics.Rect>);
method public boolean isLockscreenLiveWallpaperEnabled();
method @Nullable public android.graphics.Rect peekBitmapDimensions();
@@ -4537,7 +4538,6 @@ package android.window {
field public final int displayId;
field public final boolean isDuplicateTouchToWallpaper;
field public final boolean isFocusable;
- field public final boolean isPreventSplitting;
field public final boolean isTouchable;
field public final boolean isTrustedOverlay;
field public final boolean isVisible;
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 72450999993d..ddc1ae29f6df 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -30,7 +30,6 @@ import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.Size;
-import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
@@ -2019,23 +2018,22 @@ public class AccountManager {
* @param account the account to copy
* @param fromUser the user to copy the account from
* @param toUser the target user
- * @param callback Callback to invoke when the request completes,
- * null for no callback
* @param handler {@link Handler} identifying the callback thread,
* null for the main thread
+ * @param callback Callback to invoke when the request completes,
+ * null for no callback
* @return An {@link AccountManagerFuture} which resolves to a Boolean indicated whether it
* succeeded.
* @hide
*/
- @SuppressLint("SamShouldBeLast")
@NonNull
@SystemApi
@RequiresPermission(anyOf = {COPY_ACCOUNTS, INTERACT_ACROSS_USERS_FULL})
@FlaggedApi(FLAG_SPLIT_CREATE_MANAGED_PROFILE_ENABLED)
public AccountManagerFuture<Boolean> copyAccountToUser(
@NonNull final Account account, @NonNull final UserHandle fromUser,
- @NonNull final UserHandle toUser, @Nullable AccountManagerCallback<Boolean> callback,
- @Nullable Handler handler) {
+ @NonNull final UserHandle toUser, @Nullable Handler handler,
+ @Nullable AccountManagerCallback<Boolean> callback) {
if (account == null) throw new IllegalArgumentException("account is null");
if (toUser == null || fromUser == null) {
throw new IllegalArgumentException("fromUser and toUser cannot be null");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 8614bde775ad..4782205e3b21 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1027,9 +1027,6 @@ public class Activity extends ContextThemeWrapper
/** The autofill client controller. Always access via {@link #getAutofillClientController()}. */
private AutofillClientController mAutofillClientController;
- /** @hide */
- boolean mEnterAnimationComplete;
-
private boolean mIsInMultiWindowMode;
/** @hide */
boolean mIsInPictureInPictureMode;
@@ -1273,8 +1270,8 @@ public class Activity extends ContextThemeWrapper
* Requests to show the “Open in browser” education. “Open in browser” is a feature
* within the app header that allows users to switch from an app to the web. The feature
* is made available when an application is opened by a user clicking a link or when a
- * link is provided by an application. Links can be provided by utilizing
- * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or
+ * link is provided by an application. Links can be provided by calling
+ * {@link AssistContent#setSessionTransferUri} or
* {@link AssistContent#setWebUri}.
*
* <p>This method should be utilized when an activity wants to nudge the user to switch
@@ -1287,7 +1284,7 @@ public class Activity extends ContextThemeWrapper
* disruptive to the user to show the education and when it is optimal to switch the user to a
* browser session. Before requesting to show the education, developers should assert that they
* have set a link that can be used by the "Open in browser" feature through either
- * {@link AssistContent#EXTRA_AUTHENTICATING_USER_WEB_URI} or
+ * {@link AssistContent#setSessionTransferUri} or
* {@link AssistContent#setWebUri} so that users are navigated to a relevant page if they choose
* to switch to the browser. If a URI is not set using either method, "Open in browser" will
* utilize a generic link if available which will direct users to the homepage of the site
@@ -1296,7 +1293,7 @@ public class Activity extends ContextThemeWrapper
* the user will not be provided with the option to switch to the browser and the education will
* not be shown if requested.
*
- * @see android.app.assist.AssistContent#EXTRA_SESSION_TRANSFER_WEB_URI
+ * @see android.app.assist.AssistContent#setSessionTransferUri
*/
@FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
public final void requestOpenInBrowserEducation() {
@@ -2898,7 +2895,6 @@ public class Activity extends ContextThemeWrapper
mCalled = true;
getAutofillClientController().onActivityStopped(mIntent, mChangingConfigurations);
- mEnterAnimationComplete = false;
notifyVoiceInteractionManagerServiceActivityEvent(
VoiceInteractionSession.VOICE_INTERACTION_ACTIVITY_EVENT_STOP);
@@ -8594,8 +8590,6 @@ public class Activity extends ContextThemeWrapper
* @hide
*/
public void dispatchEnterAnimationComplete() {
- mEnterAnimationComplete = true;
- mInstrumentation.onEnterAnimationComplete();
onEnterAnimationComplete();
if (getWindow() != null && getWindow().getDecorView() != null) {
View decorView = getWindow().getDecorView();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 717a2acb4b4a..1f3e6559a695 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -105,6 +105,7 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.ResourceTimer;
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.loader.ResourcesLoader;
@@ -5253,6 +5254,7 @@ public final class ActivityThread extends ClientTransactionHandler
Resources.dumpHistory(pw, "");
pw.flush();
+ ResourceTimer.dumpTimers(info.fd.getFileDescriptor(), "-refresh");
if (info.finishCallback != null) {
info.finishCallback.sendResult(null);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 7eacaac29d4b..b611acf79bc3 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -59,7 +59,6 @@ import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.Window;
import android.view.WindowManagerGlobal;
@@ -137,7 +136,6 @@ public class Instrumentation {
private PerformanceCollector mPerformanceCollector;
private Bundle mPerfMetrics = new Bundle();
private UiAutomation mUiAutomation;
- private final Object mAnimationCompleteLock = new Object();
@RavenwoodKeep
public Instrumentation() {
@@ -455,31 +453,6 @@ public class Instrumentation {
idler.waitForIdle();
}
- private void waitForEnterAnimationComplete(Activity activity) {
- synchronized (mAnimationCompleteLock) {
- long timeout = 5000;
- try {
- // We need to check that this specified Activity completed the animation, not just
- // any Activity. If it was another Activity, then decrease the timeout by how long
- // it's already waited and wait for the thread to wakeup again.
- while (timeout > 0 && !activity.mEnterAnimationComplete) {
- long startTime = System.currentTimeMillis();
- mAnimationCompleteLock.wait(timeout);
- long totalTime = System.currentTimeMillis() - startTime;
- timeout -= totalTime;
- }
- } catch (InterruptedException e) {
- }
- }
- }
-
- /** @hide */
- public void onEnterAnimationComplete() {
- synchronized (mAnimationCompleteLock) {
- mAnimationCompleteLock.notifyAll();
- }
- }
-
/**
* Execute a call on the application's main thread, blocking until it is
* complete. Useful for doing things that are not thread-safe, such as
@@ -640,13 +613,14 @@ public class Instrumentation {
activity = aw.activity;
}
- // Do not call this method within mSync, lest it could block the main thread.
- waitForEnterAnimationComplete(activity);
-
- // Apply an empty transaction to ensure SF has a chance to update before
- // the Activity is ready (b/138263890).
- try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
- t.apply(true);
+ // Typically, callers expect that the launched activity can receive input events after this
+ // method returns, so wait until a stable state, i.e. animation is finished and input info
+ // is updated.
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .syncInputTransactions(true /* waitForAnimations */);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
}
return activity;
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 24594ab41100..614e2aaf42e8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -774,8 +774,9 @@ public class Notification implements Parcelable
/**
* Bit to be bitwise-ored into the {@link #flags} field that should be
- * set by the system if this notification is a promoted ongoing notification, either via a
- * user setting or allowlist.
+ * set by the system if this notification is a promoted ongoing notification, both because it
+ * {@link #hasPromotableCharacteristics()} and the user has not disabled the feature for this
+ * app.
*
* Applications cannot set this flag directly, but the posting app and
* {@link android.service.notification.NotificationListenerService} can read it.
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 2e6f3e1c7f0a..57549847f05d 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -753,7 +753,7 @@ public class UiModeManager {
* <p>
* The mode can be one of:
* <ul>
- * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+ * <li><em>{@link #MODE_NIGHT_NO}</em> sets the device into
* {@code notnight} mode</li>
* <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
* {@code night} mode</li>
@@ -889,7 +889,7 @@ public class UiModeManager {
* <p>
* The mode can be one of:
* <ul>
- * <li><em>{@link #MODE_NIGHT_NO}<em> sets the device into
+ * <li><em>{@link #MODE_NIGHT_NO}</em> sets the device into
* {@code notnight} mode</li>
* <li><em>{@link #MODE_NIGHT_YES}</em> sets the device into
* {@code night} mode</li>
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 360376da618c..73ecc7199686 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1690,7 +1690,7 @@ public class WallpaperManager {
* @hide
*/
@FlaggedApi(FLAG_CUSTOMIZATION_PACKS_APIS)
- @SystemApi
+ @TestApi
@RequiresPermission(READ_WALLPAPER_INTERNAL)
@NonNull
public SparseArray<Rect> getBitmapCrops(@SetWallpaperFlags int which) {
diff --git a/core/java/android/app/assist/AssistContent.java b/core/java/android/app/assist/AssistContent.java
index 3e3ca2488bd3..adf8c94ff8d5 100644
--- a/core/java/android/app/assist/AssistContent.java
+++ b/core/java/android/app/assist/AssistContent.java
@@ -1,6 +1,7 @@
package android.app.assist;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.Intent;
@@ -30,31 +31,6 @@ public class AssistContent implements Parcelable {
public static final String EXTRA_APP_FUNCTION_DATA =
"android.app.assist.extra.APP_FUNCTION_DATA";
- /**
- * This extra can be optionally supplied in the {@link #getExtras} bundle to provide a
- * {@link Uri} which will be utilized when transitioning a user's session to another surface.
- *
- * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the
- * "Open in browser" feature will use this URI to transition the current session from one
- * surface to the other. Apps may choose to encode session or user information into this
- * URI in order to provide a better session transfer experience.
- *
- * <p>Unlike {@link #setWebUri}, this URI will not be used for features where the user might
- * accidentally share it with another user. However, developers should not encode
- * authentication credentials into this URI, because it will be surfaced in the browser URL
- * bar and may be copied and shared from there.
- *
- * <p>When providing this extra, developers should still continue to provide
- * {@link #setWebUri} for backwards compatibility with features such as
- * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing">
- * recents URL sharing</a> which do not benefit from a session-transfer web URI.
- *
- * @see android.app.Activity#requestOpenInBrowserEducation()
- */
- @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
- public static final String EXTRA_SESSION_TRANSFER_WEB_URI =
- "android.app.assist.extra.SESSION_TRANSFER_WEB_URI";
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mIsAppProvidedIntent = false;
private boolean mIsAppProvidedWebUri = false;
@@ -66,6 +42,7 @@ public class AssistContent implements Parcelable {
private ClipData mClipData;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private Uri mUri;
+ private Uri mSessionTransferUri;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final Bundle mExtras;
@@ -200,6 +177,41 @@ public class AssistContent implements Parcelable {
}
/**
+ * This method can be used to provide a {@link Uri} which will be utilized when transitioning a
+ * user's session to another surface.
+ *
+ * <p>If provided, instead of using the URI provided in {@link #setWebUri}, the
+ * "Open in browser" feature will use this URI to transition the current session from one
+ * surface to the other. Apps may choose to encode session or user information into this
+ * URI in order to provide a better session transfer experience. However, while this URI will
+ * only be available to the system and not other applications, developers should not encode
+ * authentication credentials into this URI, because it will be surfaced in the browser URL bar
+ * and may be copied and shared from there.
+ *
+ * <p>When providing this URI, developers should still continue to provide
+ * {@link #setWebUri} for backwards compatibility with features such as
+ * <a href="https://developer.android.com/guide/components/activities/recents#url-sharing">
+ * recents URL sharing</a> which facilitate link sharing with other users and would not benefit
+ * from a session-transfer URI.
+ *
+ * @see android.app.Activity#requestOpenInBrowserEducation()
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+ public void setSessionTransferUri(@Nullable Uri uri) {
+ mSessionTransferUri = uri;
+ }
+
+ /**
+ * Return the content's session transfer web URI as per
+ * {@link #setSessionTransferUri(android.net.Uri)}, or null if there is none.
+ */
+ @FlaggedApi(com.android.window.flags.Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB_EDUCATION)
+ @Nullable
+ public Uri getSessionTransferUri() {
+ return mSessionTransferUri;
+ }
+
+ /**
* Return Bundle for extra vendor-specific data that can be modified and examined.
*/
public Bundle getExtras() {
@@ -218,6 +230,9 @@ public class AssistContent implements Parcelable {
mUri = Uri.CREATOR.createFromParcel(in);
}
if (in.readInt() != 0) {
+ mSessionTransferUri = Uri.CREATOR.createFromParcel(in);
+ }
+ if (in.readInt() != 0) {
mStructuredData = in.readString();
}
mIsAppProvidedIntent = in.readInt() == 1;
@@ -245,6 +260,12 @@ public class AssistContent implements Parcelable {
} else {
dest.writeInt(0);
}
+ if (mSessionTransferUri != null) {
+ dest.writeInt(1);
+ mSessionTransferUri.writeToParcel(dest, flags);
+ } else {
+ dest.writeInt(0);
+ }
if (mStructuredData != null) {
dest.writeInt(1);
dest.writeString(mStructuredData);
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig
index 46da4a3d99bc..eae50624539e 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/deprecated_flags_do_not_edit.aconfig
@@ -1,24 +1,18 @@
-# Do not add new flags to this file.
+# Do not modify this file.
#
-# Due to "virtual" keyword in the package name flags
-# added to this file cannot be accessed from C++
-# code.
+# Due to "virtual" keyword in the package name flags added to this file cannot
+# be accessed from C++ code.
#
# Use frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig
-# instead.
+# instead for new flags.
+#
+# All of the remaining flags here have been used for API flagging and are
+# therefore exported and should not be deleted.
package: "android.companion.virtual.flags"
container: "system"
flag {
- name: "enable_native_vdm"
- namespace: "virtual_devices"
- description: "Enable native VDM service"
- bug: "303535376"
- is_fixed_read_only: true
-}
-
-flag {
name: "dynamic_policy"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index de01280f293f..84af84072f1b 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -1,17 +1,11 @@
+# VirtualDeviceManager flags
#
-# Copyright (C) 2023 The Android Open Source Project
+# This file contains flags guarding features that are in development.
#
-# 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.
+# Once a flag is launched or abandoned and there are no more references to it in
+# the codebase, it should be either:
+# - deleted, or
+# - moved to launched_flags.aconfig if it was launched and used for API flagging.
package: "android.companion.virtualdevice.flags"
container: "system"
diff --git a/core/java/android/companion/virtual/flags/launched_flags.aconfig b/core/java/android/companion/virtual/flags/launched_flags.aconfig
new file mode 100644
index 000000000000..ee896319bb72
--- /dev/null
+++ b/core/java/android/companion/virtual/flags/launched_flags.aconfig
@@ -0,0 +1,6 @@
+# This file contains the launched VirtualDeviceManager flags from the
+# "android.companion.virtualdevice.flags" package that cannot be deleted because
+# they have been used for API flagging.
+
+package: "android.companion.virtualdevice.flags"
+container: "system"
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 3d75423edfa9..e3e10388754c 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12401,19 +12401,23 @@ public class Intent implements Parcelable, Cloneable {
private void collectNestedIntentKeysRecur(Set<Intent> visited) {
addExtendedFlags(EXTENDED_FLAG_NESTED_INTENT_KEYS_COLLECTED);
- if (mExtras != null && !mExtras.isEmpty()) {
+ if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
Object value;
try {
- value = mExtras.get(key);
+ // Do not unparcel any Parcelable objects. It may cause issues for app who would
+ // change class loader before it reads a parceled value. b/382633789.
+ // It is okay to not collect a parceled intent since it would have been
+ // coming from another process and collected by its containing intent already
+ // in that process.
+ if (!mExtras.isValueParceled(key)) {
+ value = mExtras.get(key);
+ } else {
+ value = null;
+ }
} catch (BadParcelableException e) {
- // This could happen when the key points to a LazyValue whose class cannot be
- // found by the classLoader - A nested object more than 1 level deeper who is
- // of type of a custom class could trigger this situation. In such case, we
- // ignore it since it is not an intent. However, it could be a custom type that
- // extends from Intent. If such an object is retrieved later in another
- // component, then trying to launch such a custom class object will fail unless
- // removeLaunchSecurityProtection() is called before it is launched.
+ // This probably would never happen. But just in case, simply ignore it since
+ // it is not an intent anyway.
value = null;
}
if (value instanceof Intent intent) {
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 0333942b7f3e..9d11710a2cad 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -17,6 +17,7 @@
package android.content.pm;
import android.Manifest;
+import android.annotation.NonNull;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -30,6 +31,7 @@ import android.os.Environment;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.IntArray;
@@ -45,11 +47,11 @@ import com.android.internal.util.ArrayUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import libcore.io.IoUtils;
-
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
+import libcore.io.IoUtils;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -94,6 +96,9 @@ public abstract class RegisteredServicesCache<V> {
@GuardedBy("mServicesLock")
private final SparseArray<UserServices<V>> mUserServices = new SparseArray<UserServices<V>>(2);
+ @GuardedBy("mServicesLock")
+ private final ArrayMap<String, ServiceInfo<V>> mServiceInfoCaches = new ArrayMap<>();
+
private static class UserServices<V> {
@GuardedBy("mServicesLock")
final Map<V, Integer> persistentServices = Maps.newHashMap();
@@ -323,13 +328,16 @@ public abstract class RegisteredServicesCache<V> {
public final ComponentName componentName;
@UnsupportedAppUsage
public final int uid;
+ public final long lastUpdateTime;
/** @hide */
- public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName) {
+ public ServiceInfo(V type, ComponentInfo componentInfo, ComponentName componentName,
+ long lastUpdateTime) {
this.type = type;
this.componentInfo = componentInfo;
this.componentName = componentName;
this.uid = (componentInfo != null) ? componentInfo.applicationInfo.uid : -1;
+ this.lastUpdateTime = lastUpdateTime;
}
@Override
@@ -490,7 +498,7 @@ public abstract class RegisteredServicesCache<V> {
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);
for (ResolveInfo resolveInfo : resolveInfos) {
try {
- ServiceInfo<V> info = parseServiceInfo(resolveInfo);
+ ServiceInfo<V> info = parseServiceInfo(resolveInfo, userId);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
@@ -638,13 +646,31 @@ public abstract class RegisteredServicesCache<V> {
}
@VisibleForTesting
- protected ServiceInfo<V> parseServiceInfo(ResolveInfo service)
+ protected ServiceInfo<V> parseServiceInfo(ResolveInfo service, int userId)
throws XmlPullParserException, IOException {
android.content.pm.ServiceInfo si = service.serviceInfo;
ComponentName componentName = new ComponentName(si.packageName, si.name);
PackageManager pm = mContext.getPackageManager();
+ // Check if the service has been in the service cache.
+ long lastUpdateTime = -1;
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfoAsUser(si.packageName,
+ PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ lastUpdateTime = packageInfo.lastUpdateTime;
+
+ ServiceInfo<V> serviceInfo = getServiceInfoFromServiceCache(si, lastUpdateTime);
+ if (serviceInfo != null) {
+ return serviceInfo;
+ }
+ } catch (NameNotFoundException | SecurityException e) {
+ Slog.d(TAG, "Fail to get the PackageInfo in parseServiceInfo: " + e);
+ }
+ }
+
XmlResourceParser parser = null;
try {
parser = si.loadXmlMetaData(pm, mMetaDataName);
@@ -670,8 +696,13 @@ public abstract class RegisteredServicesCache<V> {
if (v == null) {
return null;
}
- final android.content.pm.ServiceInfo serviceInfo = service.serviceInfo;
- return new ServiceInfo<V>(v, serviceInfo, componentName);
+ ServiceInfo<V> serviceInfo = new ServiceInfo<V>(v, si, componentName, lastUpdateTime);
+ if (Flags.optimizeParsingInRegisteredServicesCache()) {
+ synchronized (mServicesLock) {
+ mServiceInfoCaches.put(getServiceCacheKey(si), serviceInfo);
+ }
+ }
+ return serviceInfo;
} catch (NameNotFoundException e) {
throw new XmlPullParserException(
"Unable to load resources for pacakge " + si.packageName);
@@ -841,4 +872,28 @@ public abstract class RegisteredServicesCache<V> {
mContext.unregisterReceiver(mExternalReceiver);
mContext.unregisterReceiver(mUserRemovedReceiver);
}
+
+ private static String getServiceCacheKey(@NonNull android.content.pm.ServiceInfo serviceInfo) {
+ StringBuilder sb = new StringBuilder(serviceInfo.packageName);
+ sb.append('-');
+ sb.append(serviceInfo.name);
+ return sb.toString();
+ }
+
+ private ServiceInfo<V> getServiceInfoFromServiceCache(
+ @NonNull android.content.pm.ServiceInfo serviceInfo, long lastUpdateTime) {
+ String serviceCacheKey = getServiceCacheKey(serviceInfo);
+ synchronized (mServicesLock) {
+ ServiceInfo<V> serviceCache = mServiceInfoCaches.get(serviceCacheKey);
+ if (serviceCache == null) {
+ return null;
+ }
+ if (serviceCache.lastUpdateTime == lastUpdateTime) {
+ return serviceCache;
+ }
+ // The service is not latest, remove it from the cache.
+ mServiceInfoCaches.remove(serviceCacheKey);
+ return null;
+ }
+ }
}
diff --git a/core/java/android/content/pm/SystemFeaturesCache.aidl b/core/java/android/content/pm/SystemFeaturesCache.aidl
new file mode 100644
index 000000000000..18c1830a1859
--- /dev/null
+++ b/core/java/android/content/pm/SystemFeaturesCache.aidl
@@ -0,0 +1,19 @@
+/*
+** 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 android.content.pm;
+
+parcelable SystemFeaturesCache;
diff --git a/core/java/android/content/pm/SystemFeaturesCache.java b/core/java/android/content/pm/SystemFeaturesCache.java
new file mode 100644
index 000000000000..c41a7abbbc35
--- /dev/null
+++ b/core/java/android/content/pm/SystemFeaturesCache.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * A simple cache for SDK-defined system feature versions.
+ *
+ * The dense representation minimizes any per-process memory impact (<1KB). The tradeoff is that
+ * custom, non-SDK defined features are not captured by the cache, for which we can rely on the
+ * usual IPC cache for related queries.
+ *
+ * @hide
+ */
+public final class SystemFeaturesCache implements Parcelable {
+
+ // Sentinel value used for SDK-declared features that are unavailable on the current device.
+ private static final int UNAVAILABLE_FEATURE_VERSION = Integer.MIN_VALUE;
+
+ // An array of versions for SDK-defined features, from [0, PackageManager.SDK_FEATURE_COUNT).
+ @NonNull
+ private final int[] mSdkFeatureVersions;
+
+ /**
+ * Populates the cache from the set of all available {@link FeatureInfo} definitions.
+ *
+ * System features declared in {@link PackageManager} will be entered into the cache based on
+ * availability in this feature set. Other custom system features will be ignored.
+ */
+ public SystemFeaturesCache(@NonNull ArrayMap<String, FeatureInfo> availableFeatures) {
+ this(availableFeatures.values());
+ }
+
+ @VisibleForTesting
+ public SystemFeaturesCache(@NonNull Collection<FeatureInfo> availableFeatures) {
+ // First set all SDK-defined features as unavailable.
+ mSdkFeatureVersions = new int[PackageManager.SDK_FEATURE_COUNT];
+ Arrays.fill(mSdkFeatureVersions, UNAVAILABLE_FEATURE_VERSION);
+
+ // Then populate SDK-defined feature versions from the full set of runtime features.
+ for (FeatureInfo fi : availableFeatures) {
+ int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(fi.name);
+ if (sdkFeatureIndex >= 0) {
+ mSdkFeatureVersions[sdkFeatureIndex] = fi.version;
+ }
+ }
+ }
+
+ /** Only used by @{code CREATOR.createFromParcel(...)} */
+ private SystemFeaturesCache(@NonNull Parcel parcel) {
+ final int[] featureVersions = parcel.createIntArray();
+ if (featureVersions == null) {
+ throw new IllegalArgumentException(
+ "Parceled SDK feature versions should never be null");
+ }
+ if (featureVersions.length != PackageManager.SDK_FEATURE_COUNT) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Unexpected cached SDK feature count: %d (expected %d)",
+ featureVersions.length, PackageManager.SDK_FEATURE_COUNT));
+ }
+ mSdkFeatureVersions = featureVersions;
+ }
+
+ /**
+ * @return Whether the given feature is available (for SDK-defined features), otherwise null.
+ */
+ public Boolean maybeHasFeature(@NonNull String featureName, int version) {
+ // Features defined outside of the SDK aren't cached.
+ int sdkFeatureIndex = PackageManager.maybeGetSdkFeatureIndex(featureName);
+ if (sdkFeatureIndex < 0) {
+ return null;
+ }
+
+ // As feature versions can in theory collide with our sentinel value, in the (extremely)
+ // unlikely event that the queried version matches the sentinel value, we can't distinguish
+ // between an unavailable feature and a feature with the defined sentinel value.
+ if (version == UNAVAILABLE_FEATURE_VERSION
+ && mSdkFeatureVersions[sdkFeatureIndex] == UNAVAILABLE_FEATURE_VERSION) {
+ return null;
+ }
+
+ return mSdkFeatureVersions[sdkFeatureIndex] >= version;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeIntArray(mSdkFeatureVersions);
+ }
+
+ @NonNull
+ public static final Parcelable.Creator<SystemFeaturesCache> CREATOR =
+ new Parcelable.Creator<SystemFeaturesCache>() {
+
+ @Override
+ public SystemFeaturesCache createFromParcel(Parcel parcel) {
+ return new SystemFeaturesCache(parcel);
+ }
+
+ @Override
+ public SystemFeaturesCache[] newArray(int size) {
+ return new SystemFeaturesCache[size];
+ }
+ };
+}
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index 7bba06c87813..e4b8c90d381d 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -383,3 +383,11 @@ flag {
bug: "334024639"
description: "Feature flag to check whether a given UID can access a content provider"
}
+
+flag {
+ name: "optimize_parsing_in_registered_services_cache"
+ namespace: "package_manager_service"
+ description: "Feature flag to optimize RegisteredServicesCache ServiceInfo parsing by using caches."
+ bug: "319137634"
+ is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 908999b64961..b938aac811fd 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -25,6 +25,7 @@ import android.content.res.loader.ResourcesProvider;
import android.ravenwood.annotation.RavenwoodClassLoadHook;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
import android.text.TextUtils;
+import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -50,6 +51,7 @@ import java.util.Objects;
@RavenwoodKeepWholeClass
@RavenwoodClassLoadHook(RavenwoodClassLoadHook.LIBANDROID_LOADING_HOOK)
public final class ApkAssets {
+ private static final boolean DEBUG = false;
/**
* The apk assets contains framework resource values specified by the system.
@@ -134,6 +136,17 @@ public final class ApkAssets {
@Nullable
private final AssetsProvider mAssets;
+ @NonNull
+ private String mName;
+
+ private static final int UPTODATE_FALSE = 0;
+ private static final int UPTODATE_TRUE = 1;
+ private static final int UPTODATE_ALWAYS_TRUE = 2;
+
+ // Start with the only value that may change later and would force a native call to
+ // double check it.
+ private int mPreviousUpToDateResult = UPTODATE_TRUE;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -304,7 +317,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, path);
Objects.requireNonNull(path, "path");
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
@@ -313,7 +326,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, friendlyName);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
@@ -323,7 +336,7 @@ public final class ApkAssets {
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
- this(format, flags, assets);
+ this(format, flags, assets, friendlyName);
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
@@ -331,16 +344,17 @@ public final class ApkAssets {
}
private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
- this(FORMAT_APK, flags, assets);
+ this(FORMAT_APK, flags, assets, "empty");
mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
}
private ApkAssets(@FormatType int format, @PropertyFlags int flags,
- @Nullable AssetsProvider assets) {
+ @Nullable AssetsProvider assets, @NonNull String name) {
mFlags = flags;
mAssets = assets;
mIsOverlay = format == FORMAT_IDMAP;
+ if (DEBUG) mName = name;
}
@UnsupportedAppUsage
@@ -421,13 +435,41 @@ public final class ApkAssets {
}
}
+ private static double intervalMs(long beginNs, long endNs) {
+ return (endNs - beginNs) / 1000000.0;
+ }
+
/**
* Returns false if the underlying APK was changed since this ApkAssets was loaded.
*/
public boolean isUpToDate() {
+ // This function is performance-critical - it's called multiple times on every Resources
+ // object creation, and on few other cache accesses - so it's important to avoid the native
+ // call when we know for sure what it will return (which is the case for both ALWAYS_TRUE
+ // and FALSE).
+ if (mPreviousUpToDateResult != UPTODATE_TRUE) {
+ return mPreviousUpToDateResult == UPTODATE_ALWAYS_TRUE;
+ }
+ final long beforeTs, afterLockTs, afterNativeTs, afterUnlockTs;
+ if (DEBUG) beforeTs = System.nanoTime();
+ final int res;
synchronized (this) {
- return nativeIsUpToDate(mNativePtr);
+ if (DEBUG) afterLockTs = System.nanoTime();
+ res = nativeIsUpToDate(mNativePtr);
+ if (DEBUG) afterNativeTs = System.nanoTime();
+ }
+ if (DEBUG) {
+ afterUnlockTs = System.nanoTime();
+ if (afterUnlockTs - beforeTs >= 10L * 1000000) {
+ Log.d("ApkAssets", "isUpToDate(" + mName + ") took "
+ + intervalMs(beforeTs, afterUnlockTs)
+ + " ms: " + intervalMs(beforeTs, afterLockTs)
+ + " / " + intervalMs(afterLockTs, afterNativeTs)
+ + " / " + intervalMs(afterNativeTs, afterUnlockTs));
+ }
}
+ mPreviousUpToDateResult = res;
+ return res != UPTODATE_FALSE;
}
public boolean isSystem() {
@@ -487,7 +529,7 @@ public final class ApkAssets {
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native @NonNull String nativeGetDebugName(long ptr);
private static native long nativeGetStringBlock(long ptr);
- @CriticalNative private static native boolean nativeIsUpToDate(long ptr);
+ @CriticalNative private static native int nativeIsUpToDate(long ptr);
private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
private static native @Nullable OverlayableInfo nativeGetOverlayableInfo(long ptr,
String overlayableName) throws IOException;
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index d51f64ce8106..2d1bf4d9d296 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -17,13 +17,10 @@
package android.content.res;
import android.annotation.NonNull;
-import android.annotation.Nullable;
-
import android.app.AppProtoEnums;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -33,6 +30,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FrameworkStatsLog;
+import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -277,38 +275,40 @@ public final class ResourceTimer {
* Update the metrics information and dump it.
* @hide
*/
- public static void dumpTimers(@NonNull ParcelFileDescriptor pfd, @Nullable String[] args) {
- FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
- PrintWriter pw = new FastPrintWriter(fout);
- synchronized (sLock) {
- if (!sEnabled || (sConfig == null)) {
+ public static void dumpTimers(@NonNull FileDescriptor fd, String... args) {
+ try (PrintWriter pw = new FastPrintWriter(new FileOutputStream(fd))) {
+ pw.println("\nDumping ResourceTimers");
+
+ final boolean enabled;
+ synchronized (sLock) {
+ enabled = sEnabled && sConfig != null;
+ }
+ if (!enabled) {
pw.println(" Timers are not enabled in this process");
- pw.flush();
return;
}
- }
- // Look for the --refresh switch. If the switch is present, then sTimers is updated.
- // Otherwise, the current value of sTimers is displayed.
- boolean refresh = Arrays.asList(args).contains("-refresh");
-
- synchronized (sLock) {
- update(refresh);
- long runtime = sLastUpdated - sProcessStart;
- pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
- for (int i = 0; i < sTimers.length; i++) {
- Timer t = sTimers[i];
- if (t.count != 0) {
- String name = sConfig.timers[i];
- pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
- + "largest=%s\n",
- name, t.count, t.total / t.count, t.mintime, t.maxtime,
- packedString(t.percentile),
- packedString(t.largest));
+ // Look for the --refresh switch. If the switch is present, then sTimers is updated.
+ // Otherwise, the current value of sTimers is displayed.
+ boolean refresh = Arrays.asList(args).contains("-refresh");
+
+ synchronized (sLock) {
+ update(refresh);
+ long runtime = sLastUpdated - sProcessStart;
+ pw.format(" config runtime=%d proc=%s\n", runtime, Process.myProcessName());
+ for (int i = 0; i < sTimers.length; i++) {
+ Timer t = sTimers[i];
+ if (t.count != 0) {
+ String name = sConfig.timers[i];
+ pw.format(" stats timer=%s cnt=%d avg=%d min=%d max=%d pval=%s "
+ + "largest=%s\n",
+ name, t.count, t.total / t.count, t.mintime, t.maxtime,
+ packedString(t.percentile),
+ packedString(t.largest));
+ }
}
}
}
- pw.flush();
}
// Enable (or disabled) the runtime timers. Note that timers are disabled by default. This
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 2d3d25217357..868429c30631 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,8 +16,6 @@
package android.hardware;
-import static android.companion.virtual.VirtualDeviceManager.ACTION_VIRTUAL_DEVICE_REMOVED;
-import static android.companion.virtual.VirtualDeviceManager.EXTRA_VIRTUAL_DEVICE_ID;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
import static android.content.Context.DEVICE_ID_DEFAULT;
@@ -164,11 +162,7 @@ public class SystemSensorManager extends SensorManager {
// initialize the sensor list
for (int index = 0;; ++index) {
Sensor sensor = new Sensor();
- if (android.companion.virtual.flags.Flags.enableNativeVdm()) {
- if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
- } else {
- if (!nativeGetSensorAtIndex(mNativeInstance, sensor, index)) break;
- }
+ if (!nativeGetDefaultDeviceSensorAtIndex(mNativeInstance, sensor, index)) break;
mFullSensorsList.add(sensor);
mHandleToSensor.put(sensor.getHandle(), sensor);
}
@@ -555,11 +549,7 @@ public class SystemSensorManager extends SensorManager {
}
private List<Sensor> createRuntimeSensorListLocked(int deviceId) {
- if (android.companion.virtual.flags.Flags.vdmPublicApis()) {
- setupVirtualDeviceListener();
- } else {
- setupRuntimeSensorBroadcastReceiver();
- }
+ setupVirtualDeviceListener();
List<Sensor> list = new ArrayList<>();
nativeGetRuntimeSensors(mNativeInstance, deviceId, list);
mFullRuntimeSensorListByDevice.put(deviceId, list);
@@ -570,35 +560,6 @@ public class SystemSensorManager extends SensorManager {
return list;
}
- private void setupRuntimeSensorBroadcastReceiver() {
- if (mRuntimeSensorBroadcastReceiver == null) {
- mRuntimeSensorBroadcastReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(ACTION_VIRTUAL_DEVICE_REMOVED)) {
- synchronized (mFullRuntimeSensorListByDevice) {
- final int deviceId = intent.getIntExtra(
- EXTRA_VIRTUAL_DEVICE_ID, DEVICE_ID_DEFAULT);
- List<Sensor> removedSensors =
- mFullRuntimeSensorListByDevice.removeReturnOld(deviceId);
- if (removedSensors != null) {
- for (Sensor s : removedSensors) {
- cleanupSensorConnection(s);
- }
- }
- mRuntimeSensorListByDeviceByType.remove(deviceId);
- }
- }
- }
- };
-
- IntentFilter filter = new IntentFilter("virtual_device_removed");
- filter.addAction(ACTION_VIRTUAL_DEVICE_REMOVED);
- mContext.registerReceiver(mRuntimeSensorBroadcastReceiver, filter,
- Context.RECEIVER_NOT_EXPORTED);
- }
- }
-
private void setupVirtualDeviceListener() {
if (mVirtualDeviceListener != null) {
return;
diff --git a/core/java/android/hardware/display/DisplayTopology.java b/core/java/android/hardware/display/DisplayTopology.java
index 0e2c05f92e7c..1d2f133ee759 100644
--- a/core/java/android/hardware/display/DisplayTopology.java
+++ b/core/java/android/hardware/display/DisplayTopology.java
@@ -679,8 +679,7 @@ public final class DisplayTopology implements Parcelable {
}
/**
- * Tests whether two brightness float values are within a small enough tolerance
- * of each other.
+ * Tests whether two float values are within a small enough tolerance of each other.
* @param a first float to compare
* @param b second float to compare
* @return whether the two values are within a small enough tolerance value
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index 8da630c95135..b380e259577c 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -78,6 +78,24 @@ public class InputSettings {
public static final int DEFAULT_POINTER_SPEED = 0;
/**
+ * Pointer Speed: The minimum (slowest) mouse scrolling speed (-7).
+ * @hide
+ */
+ public static final int MIN_MOUSE_SCROLLING_SPEED = -7;
+
+ /**
+ * Pointer Speed: The maximum (fastest) mouse scrolling speed (7).
+ * @hide
+ */
+ public static final int MAX_MOUSE_SCROLLING_SPEED = 7;
+
+ /**
+ * Pointer Speed: The default mouse scrolling speed (0).
+ * @hide
+ */
+ public static final int DEFAULT_MOUSE_SCROLLING_SPEED = 0;
+
+ /**
* Bounce Keys Threshold: The default value of the threshold (500 ms).
*
* @hide
@@ -650,6 +668,54 @@ public class InputSettings {
}
/**
+ * Gets the mouse scrolling speed.
+ *
+ * The returned value only applies when mouse scrolling acceleration is not enabled.
+ *
+ * @param context The application context.
+ * @return The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED} and
+ * {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value
+ * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}.
+ *
+ * @hide
+ */
+ public static int getMouseScrollingSpeed(@NonNull Context context) {
+ if (!isMouseScrollingAccelerationFeatureFlagEnabled()) {
+ return 0;
+ }
+
+ return Settings.System.getIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SCROLLING_SPEED, DEFAULT_MOUSE_SCROLLING_SPEED,
+ UserHandle.USER_CURRENT);
+ }
+
+ /**
+ * Sets the mouse scrolling speed, and saves it in the settings.
+ *
+ * The new speed will only apply when mouse scrolling acceleration is not enabled.
+ *
+ * @param context The application context.
+ * @param speed The mouse scrolling speed as a value between {@link #MIN_MOUSE_SCROLLING_SPEED}
+ * and {@link #MAX_MOUSE_SCROLLING_SPEED}, or the default value
+ * {@link #DEFAULT_MOUSE_SCROLLING_SPEED}.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+ public static void setMouseScrollingSpeed(@NonNull Context context, int speed) {
+ if (isMouseScrollingAccelerationEnabled(context)) {
+ return;
+ }
+
+ if (speed < MIN_MOUSE_SCROLLING_SPEED || speed > MAX_MOUSE_SCROLLING_SPEED) {
+ throw new IllegalArgumentException("speed out of range");
+ }
+
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.MOUSE_SCROLLING_SPEED, speed, UserHandle.USER_CURRENT);
+ }
+
+ /**
* Whether mouse vertical scrolling is reversed. This applies only to connected mice.
*
* @param context The application context.
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 66d073fa791e..cb1e0161441f 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -129,6 +129,7 @@ public final class KeyGestureEvent {
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT = 79;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP = 80;
public static final int KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN = 81;
+ public static final int KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS = 82;
public static final int FLAG_CANCELLED = 1;
@@ -225,6 +226,7 @@ public final class KeyGestureEvent {
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_RIGHT,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP,
KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN,
+ KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -807,6 +809,8 @@ public final class KeyGestureEvent {
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_UP";
case KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN:
return "KEY_GESTURE_TYPE_MAGNIFICATION_PAN_DOWN";
+ case KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return "KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java
index ecd90e46e432..1041041b2a27 100644
--- a/core/java/android/os/BaseBundle.java
+++ b/core/java/android/os/BaseBundle.java
@@ -386,6 +386,15 @@ public class BaseBundle implements Parcel.ClassLoaderProvider {
}
/**
+ * return true if the value corresponding to this key is still parceled.
+ * @hide
+ */
+ public boolean isValueParceled(String key) {
+ if (mMap == null) return true;
+ int i = mMap.indexOfKey(key);
+ return (mMap.valueAt(i) instanceof BiFunction<?, ?, ?>);
+ }
+ /**
* Returns the value for a certain position in the array map for expected return type {@code
* clazz} (or pass {@code null} for no type check).
*
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 8f6a50843ddb..12080ca511b2 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -78,6 +78,9 @@ public class GraphicsEnvironment {
private static final String PROPERTY_GFX_DRIVER_PRERELEASE = "ro.gfx.driver.1";
private static final String PROPERTY_GFX_DRIVER_BUILD_TIME = "ro.gfx.driver_build_time";
+ /// System properties related to EGL
+ private static final String PROPERTY_RO_HARDWARE_EGL = "ro.hardware.egl";
+
// Metadata flags within the <application> tag in the AndroidManifest.xml file.
private static final String METADATA_DRIVER_BUILD_TIME =
"com.android.graphics.driver.build_time";
@@ -504,9 +507,11 @@ public class GraphicsEnvironment {
final List<ResolveInfo> resolveInfos =
pm.queryIntentActivities(intent, PackageManager.MATCH_SYSTEM_ONLY);
- if (resolveInfos.size() != 1) {
- Log.v(TAG, "Invalid number of ANGLE packages. Required: 1, Found: "
- + resolveInfos.size());
+ if (resolveInfos.isEmpty()) {
+ Log.v(TAG, "No ANGLE packages installed.");
+ return "";
+ } else if (resolveInfos.size() > 1) {
+ Log.v(TAG, "Too many ANGLE packages found: " + resolveInfos.size());
if (DEBUG) {
for (ResolveInfo resolveInfo : resolveInfos) {
Log.d(TAG, "Found ANGLE package: " + resolveInfo.activityInfo.packageName);
@@ -516,7 +521,7 @@ public class GraphicsEnvironment {
}
// Must be exactly 1 ANGLE PKG found to get here.
- return resolveInfos.get(0).activityInfo.packageName;
+ return resolveInfos.getFirst().activityInfo.packageName;
}
/**
@@ -545,10 +550,12 @@ public class GraphicsEnvironment {
}
/**
- * Determine whether ANGLE should be used, attempt to set up from apk first, if ANGLE can be
- * set up from apk, pass ANGLE details down to the C++ GraphicsEnv class via
- * GraphicsEnv::setAngleInfo(). If apk setup fails, attempt to set up to use system ANGLE.
- * Return false if both fail.
+ * If ANGLE is not the system driver, determine whether ANGLE should be used, and if so, pass
+ * down the necessary details to the C++ GraphicsEnv class via GraphicsEnv::setAngleInfo().
+ * <p>
+ * If ANGLE is the system driver or the various flags indicate it should be used, attempt to
+ * set up ANGLE from the APK first, so the updatable libraries are used. If APK setup fails,
+ * attempt to set up the system ANGLE. Return false if both fail.
*
* @param context - Context of the application.
* @param bundle - Bundle of the application.
@@ -559,15 +566,26 @@ public class GraphicsEnvironment {
*/
private boolean setupAngle(Context context, Bundle bundle, PackageManager packageManager,
String packageName) {
- final String angleChoice = queryAngleChoice(context, bundle, packageName);
- if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
- return false;
- }
- if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
- nativeSetAngleInfo("", true, packageName, null);
- return false;
+ final String eglDriverName = SystemProperties.get(PROPERTY_RO_HARDWARE_EGL);
+
+ // The ANGLE choice only makes sense if ANGLE is not the system driver.
+ if (!eglDriverName.equals(ANGLE_DRIVER_NAME)) {
+ final String angleChoice = queryAngleChoice(context, bundle, packageName);
+ if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_DEFAULT)) {
+ return false;
+ }
+ if (angleChoice.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
+ nativeSetAngleInfo("", true, packageName, null);
+ return false;
+ }
}
+ // If we reach here, it means either:
+ // 1. system driver is not ANGLE, but ANGLE is requested.
+ // 2. system driver is ANGLE.
+ // In both cases, setup ANGLE info. We attempt to setup the APK first, so
+ // updated/development libraries are used if the APK is present, falling back to the system
+ // libraries otherwise.
return setupAngleFromApk(context, bundle, packageManager, packageName)
|| setupAngleFromSystem(context, bundle, packageName);
}
@@ -605,7 +623,6 @@ public class GraphicsEnvironment {
if (angleInfo == null) {
anglePkgName = getAnglePackageName(packageManager);
if (TextUtils.isEmpty(anglePkgName)) {
- Log.v(TAG, "Failed to find ANGLE package.");
return false;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index c94526bcdcd7..ec58eff92410 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6372,6 +6372,19 @@ public final class Settings {
"mouse_pointer_acceleration_enabled";
/**
+ * Mouse scrolling speed setting.
+ *
+ * This is an integer value in a range between -7 and +7, so there are 15 possible values.
+ * The setting only applies when mouse scrolling acceleration is not enabled.
+ * -7 = slowest
+ * 0 = default speed
+ * +7 = fastest
+ *
+ * @hide
+ */
+ public static final String MOUSE_SCROLLING_SPEED = "mouse_scrolling_speed";
+
+ /**
* Pointer fill style, specified by
* {@link android.view.PointerIcon.PointerIconVectorStyleFill} constants.
*
@@ -6623,6 +6636,7 @@ public final class Settings {
PRIVATE_SETTINGS.add(MOUSE_POINTER_ACCELERATION_ENABLED);
PRIVATE_SETTINGS.add(PREFERRED_REGION);
PRIVATE_SETTINGS.add(MOUSE_SCROLLING_ACCELERATION);
+ PRIVATE_SETTINGS.add(MOUSE_SCROLLING_SPEED);
}
/**
@@ -9305,6 +9319,16 @@ public final class Settings {
"accessibility_autoclick_delay";
/**
+ * Integer setting specifying the autoclick cursor area size (the radius of the autoclick
+ * ring indicator) when {@link #ACCESSIBILITY_AUTOCLICK_ENABLED} is set.
+ *
+ * @see #ACCESSIBILITY_AUTOCLICK_ENABLED
+ * @hide
+ */
+ public static final String ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE =
+ "accessibility_autoclick_cursor_area_size";
+
+ /**
* Whether or not larger size icons are used for the pointer of mouse/trackpad for
* accessibility.
* (0 = false, 1 = true)
@@ -17395,13 +17419,6 @@ public final class Settings {
/**
- * Whether back preview animations are played when user does a back gesture or presses
- * the back button.
- * @hide
- */
- public static final String ENABLE_BACK_ANIMATION = "enable_back_animation";
-
- /**
* An allow list of packages for which the user has granted the permission to communicate
* across profiles.
*
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index e254bf3e016f..d53b98c65f9a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -77,7 +77,7 @@ public abstract class Layout {
private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0f;
private static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP = 5f;
// since we're not using soft light yet, this needs to be much lower than the spec'd 0.8
- private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.5f;
+ private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.7f;
/** @hide */
@IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java
index 5c38a1597433..195896dc8edf 100644
--- a/core/java/android/view/InputEventConsistencyVerifier.java
+++ b/core/java/android/view/InputEventConsistencyVerifier.java
@@ -81,7 +81,7 @@ public final class InputEventConsistencyVerifier {
// Bitfield of pointer ids that are currently down.
// Assumes that the largest possible pointer id is 31, which is potentially subject to change.
- // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ // (See MAX_POINTER_ID in frameworks/native/include/input/input.h)
private int mTouchEventStreamPointers;
// The device id and source of the current stream of touch events.
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 6cd4a4033adf..3e529ccf064a 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -57,7 +57,7 @@ public final class InputWindowHandle {
InputConfig.NO_INPUT_CHANNEL,
InputConfig.NOT_FOCUSABLE,
InputConfig.NOT_TOUCHABLE,
- InputConfig.PREVENT_SPLITTING,
+ InputConfig.DEPRECATED_PREVENT_SPLITTING,
InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
InputConfig.IS_WALLPAPER,
InputConfig.PAUSE_DISPATCHING,
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 833f2d98554e..e665c08c63e4 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -160,6 +160,10 @@ public final class SurfaceControl implements Parcelable {
float l, float t, float r, float b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
float cornerRadius);
+ private static native void nativeSetClientDrawnCornerRadius(long transactionObj,
+ long nativeObject, float clientDrawnCornerRadius);
+ private static native void nativeSetClientDrawnShadows(long transactionObj,
+ long nativeObject, float clientDrawnShadows);
private static native void nativeSetBackgroundBlurRadius(long transactionObj, long nativeObject,
int blurRadius);
private static native void nativeSetLayerStack(long transactionObj, long nativeObject,
@@ -3654,6 +3658,66 @@ public final class SurfaceControl implements Parcelable {
return this;
}
+
+ /**
+ * Disables corner radius of a {@link SurfaceControl}. When the radius set by
+ * {@link Transaction#setCornerRadius(SurfaceControl, float)} is equal to
+ * clientDrawnCornerRadius the corner radius drawn by SurfaceFlinger is disabled.
+ *
+ * @param sc SurfaceControl
+ * @param clientDrawnCornerRadius Corner radius drawn by the client
+ * @return Itself.
+ * @hide
+ */
+ @NonNull
+ public Transaction setClientDrawnCornerRadius(@NonNull SurfaceControl sc,
+ float clientDrawnCornerRadius) {
+ checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setClientDrawnCornerRadius", this, sc, "clientDrawnCornerRadius="
+ + clientDrawnCornerRadius);
+ }
+ if (Flags.ignoreCornerRadiusAndShadows()) {
+ nativeSetClientDrawnCornerRadius(mNativeObject, sc.mNativeObject,
+ clientDrawnCornerRadius);
+ } else {
+ Log.w(TAG, "setClientDrawnCornerRadius was called but"
+ + "ignore_corner_radius_and_shadows flag is disabled");
+ }
+
+ return this;
+ }
+
+ /**
+ * Disables shadows of a {@link SurfaceControl}. When the radius set by
+ * {@link Transaction#setClientDrawnShadows(SurfaceControl, float)} is equal to
+ * clientDrawnShadowRadius the shadows drawn by SurfaceFlinger is disabled.
+ *
+ * @param sc SurfaceControl
+ * @param clientDrawnShadowRadius Shadow radius drawn by the client
+ * @return Itself.
+ * @hide
+ */
+ @NonNull
+ public Transaction setClientDrawnShadows(@NonNull SurfaceControl sc,
+ float clientDrawnShadowRadius) {
+ checkPreconditions(sc);
+ if (SurfaceControlRegistry.sCallStackDebuggingEnabled) {
+ SurfaceControlRegistry.getProcessInstance().checkCallStackDebugging(
+ "setClientDrawnShadows", this, sc,
+ "clientDrawnShadowRadius=" + clientDrawnShadowRadius);
+ }
+ if (Flags.ignoreCornerRadiusAndShadows()) {
+ nativeSetClientDrawnShadows(mNativeObject, sc.mNativeObject,
+ clientDrawnShadowRadius);
+ } else {
+ Log.w(TAG, "setClientDrawnShadows was called but"
+ + "ignore_corner_radius_and_shadows flag is disabled");
+ }
+ return this;
+ }
+
/**
* Sets the background blur radius of the {@link SurfaceControl}.
*
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index f50ea9106a61..25bd713d9191 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -453,6 +453,7 @@ public final class WindowManagerGlobal {
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
+ Log.e(TAG, "Couldn't add view: " + view, e);
final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
// BadTokenException or InvalidDisplayException, clean up.
if (viewIndex >= 0) {
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 1f341caa8ed3..6d2c0d0061dd 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -30,7 +30,8 @@ import java.lang.annotation.RetentionPolicy;
* @hide
*/
public interface WindowManagerPolicyConstants {
- // Policy flags. These flags are also defined in frameworks/base/include/ui/Input.h and
+ // Policy flags. These flags are also defined in
+ // frameworks/native/include/input/Input.h and
// frameworks/native/libs/input/android/os/IInputConstants.aidl
int FLAG_WAKE = 0x00000001;
int FLAG_VIRTUAL = 0x00000002;
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index fd57aec4180b..544f42b9acfa 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -148,6 +148,15 @@ public final class AccessibilityManager {
/** @hide */
public static final int AUTOCLICK_DELAY_DEFAULT = 600;
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT = 60;
+
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_MIN = 20;
+
+ /** @hide */
+ public static final int AUTOCLICK_CURSOR_AREA_SIZE_MAX = 100;
+
/**
* Activity action: Launch UI to manage which accessibility service or feature is assigned
* to the navigation bar Accessibility button.
diff --git a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
index b3bd92b37357..c871d568e625 100644
--- a/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
+++ b/core/java/android/view/contentprotection/flags/content_protection_flags.aconfig
@@ -45,3 +45,10 @@ flag {
description: "If true, the APIs to manage content protection device policy will be enabled."
bug: "319477846"
}
+
+flag {
+ name: "exported_settings_activity_enabled"
+ namespace: "content_protection"
+ description: "If true, the content protection Settings Activity will be exported for launching externally."
+ bug: "385310141"
+}
diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java
index ac9bec305fff..6461f2a0fcda 100644
--- a/core/java/android/window/WindowInfosListenerForTest.java
+++ b/core/java/android/window/WindowInfosListenerForTest.java
@@ -103,12 +103,6 @@ public class WindowInfosListenerForTest {
public final boolean isFocusable;
/**
- * True if the window is preventing splitting
- */
- @SuppressLint("UnflaggedApi") // The API is only used for tests.
- public final boolean isPreventSplitting;
-
- /**
* True if the window duplicates touches received to wallpaper.
*/
@SuppressLint("UnflaggedApi") // The API is only used for tests.
@@ -133,8 +127,6 @@ public class WindowInfosListenerForTest {
this.transform = transform;
this.isTouchable = (inputConfig & InputConfig.NOT_TOUCHABLE) == 0;
this.isFocusable = (inputConfig & InputConfig.NOT_FOCUSABLE) == 0;
- this.isPreventSplitting = (inputConfig
- & InputConfig.PREVENT_SPLITTING) != 0;
this.isDuplicateTouchToWallpaper = (inputConfig
& InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER) != 0;
this.isWatchOutsideTouch = (inputConfig
diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig
index ccb1e2b4b652..be0b4fea459c 100644
--- a/core/java/android/window/flags/lse_desktop_experience.aconfig
+++ b/core/java/android/window/flags/lse_desktop_experience.aconfig
@@ -478,10 +478,10 @@ flag {
}
flag {
- name: "enable_multiple_desktops"
+ name: "enable_multiple_desktops_frontend"
namespace: "lse_desktop_experience"
- description: "Enable multiple desktop sessions for desktop windowing."
- bug: "379158791"
+ description: "Enable multiple desktop sessions for desktop windowing (frontend)."
+ bug: "362720309"
}
flag {
@@ -531,8 +531,11 @@ flag {
}
flag {
- name: "enable_desktop_wallpaper_activity_on_system_user"
+ name: "enable_desktop_wallpaper_activity_for_system_user"
namespace: "lse_desktop_experience"
description: "Enables starting DesktopWallpaperActivity on system user."
bug: "385294350"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
} \ No newline at end of file
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index bb4770768cb1..8ff2e6aebdd0 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -91,6 +91,14 @@ flag {
}
flag {
+ name: "ignore_corner_radius_and_shadows"
+ namespace: "window_surfaces"
+ description: "Ignore the corner radius and shadows of a SurfaceControl"
+ bug: "375624570"
+ is_fixed_read_only: true
+} # ignore_corner_radius_and_shadows
+
+flag {
name: "jank_api"
namespace: "window_surfaces"
description: "Adds the jank data listener to AttachedSurfaceControl"
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index a8641326b1f2..de3e0d3faf43 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -113,13 +113,6 @@ flag {
}
flag {
- name: "predictive_back_system_anims"
- namespace: "systemui"
- description: "Predictive back for system animations"
- bug: "320510464"
-}
-
-flag {
name: "remove_activity_starter_dream_callback"
namespace: "windowing_frontend"
description: "Avoid a race with DreamManagerService callbacks for isDreaming by checking Activity state directly"
diff --git a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
index 0b1ecf78d28c..d03bb5c3cb17 100644
--- a/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
+++ b/core/java/com/android/internal/accessibility/util/AccessibilityUtils.java
@@ -29,6 +29,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
import android.os.Build;
import android.os.UserHandle;
import android.provider.Settings;
@@ -351,4 +352,24 @@ public final class AccessibilityUtils {
}
return result;
}
+
+ /** Returns the {@link ComponentName} of an installed accessibility service by label. */
+ @Nullable
+ public static ComponentName getInstalledAccessibilityServiceComponentNameByLabel(
+ Context context, String label) {
+ AccessibilityManager accessibilityManager =
+ context.getSystemService(AccessibilityManager.class);
+ List<AccessibilityServiceInfo> serviceInfos =
+ accessibilityManager.getInstalledAccessibilityServiceList();
+
+ for (AccessibilityServiceInfo service : serviceInfos) {
+ final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+ if (label.equals(serviceInfo.loadLabel(context.getPackageManager()).toString())
+ && (serviceInfo.applicationInfo.isSystemApp()
+ || serviceInfo.applicationInfo.isUpdatedSystemApp())) {
+ return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+ }
+ }
+ return null;
+ }
}
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 4aebde536dcf..5635943cb76e 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -67,6 +67,7 @@ public class SystemNotificationChannels {
@Deprecated public static final String SYSTEM_CHANGES_DEPRECATED = "SYSTEM_CHANGES";
public static final String SYSTEM_CHANGES = "SYSTEM_CHANGES_ALERTS";
public static final String ACCESSIBILITY_MAGNIFICATION = "ACCESSIBILITY_MAGNIFICATION";
+ public static final String ACCESSIBILITY_HEARING_DEVICE = "ACCESSIBILITY_HEARING_DEVICE";
public static final String ACCESSIBILITY_SECURITY_POLICY = "ACCESSIBILITY_SECURITY_POLICY";
public static final String ABUSIVE_BACKGROUND_APPS = "ABUSIVE_BACKGROUND_APPS";
@@ -203,6 +204,13 @@ public class SystemNotificationChannels {
newFeaturePrompt.setBlockable(true);
channelsList.add(newFeaturePrompt);
+ final NotificationChannel accessibilityHearingDeviceChannel = new NotificationChannel(
+ ACCESSIBILITY_HEARING_DEVICE,
+ context.getString(R.string.notification_channel_accessibility_hearing_device),
+ NotificationManager.IMPORTANCE_HIGH);
+ accessibilityHearingDeviceChannel.setBlockable(true);
+ channelsList.add(accessibilityHearingDeviceChannel);
+
final NotificationChannel accessibilitySecurityPolicyChannel = new NotificationChannel(
ACCESSIBILITY_SECURITY_POLICY,
context.getString(R.string.notification_channel_accessibility_security_policy),
diff --git a/core/java/com/android/internal/policy/IKeyguardService.aidl b/core/java/com/android/internal/policy/IKeyguardService.aidl
index d62c8f378af0..73c2265d5f6e 100644
--- a/core/java/com/android/internal/policy/IKeyguardService.aidl
+++ b/core/java/com/android/internal/policy/IKeyguardService.aidl
@@ -53,21 +53,21 @@ oneway interface IKeyguardService {
*
* @param pmSleepReason One of PowerManager.GO_TO_SLEEP_REASON_*, detailing the specific reason
* we're going to sleep, such as GO_TO_SLEEP_REASON_POWER_BUTTON or GO_TO_SLEEP_REASON_TIMEOUT.
- * @param cameraGestureTriggered whether the camera gesture was triggered between
- * {@link #onStartedGoingToSleep} and this method; if it's been
- * triggered, we shouldn't lock the device.
+ * @param powerButtonLaunchGestureTriggered whether the power button double tap gesture was
+ * triggered between {@link #onStartedGoingToSleep} and this
+ * method; if it's been triggered, we shouldn't lock the device.
*/
- void onFinishedGoingToSleep(int pmSleepReason, boolean cameraGestureTriggered);
+ void onFinishedGoingToSleep(int pmSleepReason, boolean powerButtonLaunchGestureTriggered);
/**
* Called when the device has started waking up.
* @param pmWakeReason One of PowerManager.WAKE_REASON_*, detailing the reason we're waking up,
* such as WAKE_REASON_POWER_BUTTON or WAKE_REASON_GESTURE.
- * @param cameraGestureTriggered Whether we're waking up due to a power button double tap
- * gesture.
+ * @param powerButtonLaunchGestureTriggered Whether we're waking up due to a power button
+ * double tap gesture.
*/
- void onStartedWakingUp(int pmWakeReason, boolean cameraGestureTriggered);
+ void onStartedWakingUp(int pmWakeReason, boolean powerButtonLaunchGestureTriggered);
/**
* Called when the device has finished waking up.
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 1e7bfe32ba79..e6364a96bd9f 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -111,9 +111,8 @@ static void DeleteGuardedApkAssets(Guarded<AssetManager2::ApkAssetsPtr>& apk_ass
class LoaderAssetsProvider : public AssetsProvider {
public:
static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
- return (!assets_provider) ? EmptyAssetsProvider::Create()
- : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider(
- env, assets_provider));
+ return std::unique_ptr<AssetsProvider>{
+ assets_provider ? new LoaderAssetsProvider(env, assets_provider) : nullptr};
}
bool ForEachFile(const std::string& /* root_path */,
@@ -129,8 +128,8 @@ class LoaderAssetsProvider : public AssetsProvider {
return debug_name_;
}
- bool IsUpToDate() const override {
- return true;
+ UpToDate IsUpToDate() const override {
+ return UpToDate::Always;
}
~LoaderAssetsProvider() override {
@@ -212,7 +211,7 @@ class LoaderAssetsProvider : public AssetsProvider {
auto string_result = static_cast<jstring>(env->CallObjectMethod(
assets_provider_, gAssetsProviderOffsets.toString));
ScopedUtfChars str(env, string_result);
- debug_name_ = std::string(str.c_str(), str.size());
+ debug_name_ = std::string(str.c_str());
}
// The global reference to the AssetsProvider
@@ -243,10 +242,10 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
- std::move(loader_assets),
- property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFile(path.c_str()),
+ MultiAssetsProvider::Create(std::move(loader_assets)),
+ property_flags);
+ break;
case FORMAT_DIRECTORY: {
auto assets = MultiAssetsProvider::Create(std::move(loader_assets),
DirectoryAssetsProvider::Create(path.c_str()));
@@ -316,10 +315,11 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(
- AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */),
- std::move(loader_assets), property_flags);
- break;
+ apk_assets = ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+ nullptr /* path */),
+ MultiAssetsProvider::Create(std::move(loader_assets)),
+ property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -386,12 +386,15 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
break;
}
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(
- AssetsProvider::CreateAssetFromFd(std::move(dup_fd), nullptr /* path */,
- static_cast<off64_t>(offset),
- static_cast<off64_t>(length)),
- std::move(loader_assets), property_flags);
- break;
+ apk_assets =
+ ApkAssets::LoadTable(AssetsProvider::CreateAssetFromFd(std::move(dup_fd),
+ nullptr /* path */,
+ static_cast<off64_t>(offset),
+ static_cast<off64_t>(
+ length)),
+ MultiAssetsProvider::Create(std::move(loader_assets)),
+ property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -408,13 +411,16 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
- auto apk_assets = ApkAssets::Load(LoaderAssetsProvider::Create(env, assets_provider), flags);
- if (apk_assets == nullptr) {
- const std::string error_msg =
- base::StringPrintf("Failed to load empty assets with provider %p", (void*)assets_provider);
- jniThrowException(env, "java/io/IOException", error_msg.c_str());
- return 0;
- }
+ auto apk_assets = ApkAssets::Load(MultiAssetsProvider::Create(
+ LoaderAssetsProvider::Create(env, assets_provider)),
+ flags);
+ if (apk_assets == nullptr) {
+ const std::string error_msg =
+ base::StringPrintf("Failed to load empty assets with provider %p",
+ (void*)assets_provider);
+ jniThrowException(env, "java/io/IOException", error_msg.c_str());
+ return 0;
+ }
return CreateGuardedApkAssets(std::move(apk_assets));
}
@@ -443,10 +449,10 @@ static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr)
return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
}
-static jboolean NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
+static jint NativeIsUpToDate(CRITICAL_JNI_PARAMS_COMMA jlong ptr) {
auto scoped_apk_assets = ScopedLock(ApkAssetsFromLong(ptr));
auto apk_assets = scoped_apk_assets->get();
- return apk_assets->IsUpToDate() ? JNI_TRUE : JNI_FALSE;
+ return (jint)apk_assets->IsUpToDate();
}
static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
@@ -558,7 +564,7 @@ static const JNINativeMethod gApkAssetsMethods[] = {
{"nativeGetDebugName", "(J)Ljava/lang/String;", (void*)NativeGetDebugName},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
// @CriticalNative
- {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+ {"nativeIsUpToDate", "(J)I", (void*)NativeIsUpToDate},
{"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
{"nativeGetOverlayableInfo", "(JLjava/lang/String;)Landroid/content/om/OverlayableInfo;",
(void*)NativeGetOverlayableInfo},
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 0c243d1dc185..6f69e4005b80 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1113,6 +1113,22 @@ static void nativeSetCornerRadius(JNIEnv* env, jclass clazz, jlong transactionOb
transaction->setCornerRadius(ctrl, cornerRadius);
}
+static void nativeSetClientDrawnCornerRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jfloat clientDrawnCornerRadius) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setClientDrawnCornerRadius(ctrl, clientDrawnCornerRadius);
+}
+
+static void nativeSetClientDrawnShadows(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jfloat clientDrawnShadowRadius) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl*>(nativeObject);
+ transaction->setClientDrawnShadowRadius(ctrl, clientDrawnShadowRadius);
+}
+
static void nativeSetBackgroundBlurRadius(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint blurRadius) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2547,6 +2563,10 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetCrop },
{"nativeSetCornerRadius", "(JJF)V",
(void*)nativeSetCornerRadius },
+ {"nativeSetClientDrawnCornerRadius", "(JJF)V",
+ (void*) nativeSetClientDrawnCornerRadius },
+ {"nativeSetClientDrawnShadows", "(JJF)V",
+ (void*) nativeSetClientDrawnShadows },
{"nativeSetBackgroundBlurRadius", "(JJI)V",
(void*)nativeSetBackgroundBlurRadius },
{"nativeSetLayerStack", "(JJI)V",
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 96d34a0230b3..4f7ba9388a1d 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -107,6 +107,8 @@ message SecureSettingsProto {
optional SettingProto accessibility_key_gesture_targets = 59 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto hct_rect_prompt_status = 60 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto em_value = 61 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ // Settings for accessibility autoclick
+ optional SettingProto autoclick_cursor_area_size = 62 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Accessibility accessibility = 2;
diff --git a/core/proto/android/providers/settings/system.proto b/core/proto/android/providers/settings/system.proto
index 0d99200f4e6f..64c9f540a97b 100644
--- a/core/proto/android/providers/settings/system.proto
+++ b/core/proto/android/providers/settings/system.proto
@@ -229,6 +229,7 @@ message SystemSettingsProto {
optional SettingProto swap_primary_button = 2 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto scrolling_acceleration = 3 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto pointer_acceleration_enabled = 4 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto scrolling_speed = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional Mouse mouse = 38;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 45a5d85a097d..c50c5e9d3341 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4244,12 +4244,19 @@
is non-interactive. -->
<bool name="config_cameraDoubleTapPowerGestureEnabled">true</bool>
- <!-- Allow the gesture to double tap the power button to trigger a target action. -->
- <bool name="config_doubleTapPowerGestureEnabled">true</bool>
- <!-- Default target action for double tap of the power button gesture.
+ <!-- Controls the double tap power button gesture to trigger a target action.
+ 0: Gesture is disabled
+ 1: Launch camera mode, allowing the user to disable/enable the double tap power gesture
+ from launching the camera application.
+ 2: Multi target mode, allowing the user to select one of the targets defined in
+ config_doubleTapPowerGestureMultiTargetDefaultAction and to disable/enable the double
+ tap power gesture from triggering the selected target action.
+ -->
+ <integer name="config_doubleTapPowerGestureMode">2</integer>
+ <!-- Default target action for double tap of the power button gesture in multi target mode.
0: Launch camera
1: Launch wallet -->
- <integer name="config_defaultDoubleTapPowerGestureAction">0</integer>
+ <integer name="config_doubleTapPowerGestureMultiTargetDefaultAction">0</integer>
<!-- Allow the gesture to quick tap the power button multiple times to start the emergency sos
experience while the device is non-interactive. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6313054e47f5..ad9e7252c6a8 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -881,6 +881,10 @@
<string name="notification_channel_accessibility_magnification">Magnification</string>
<!-- Text shown when viewing channel settings for notifications related to accessibility
+ hearing device. [CHAR_LIMIT=NONE]-->
+ <string name="notification_channel_accessibility_hearing_device">Hearing device</string>
+
+ <!-- Text shown when viewing channel settings for notifications related to accessibility
security policy. [CHAR_LIMIT=NONE]-->
<string name="notification_channel_accessibility_security_policy">Accessibility usage</string>
@@ -4985,6 +4989,19 @@
<!-- Text used to describe system navigation features, shown within a UI allowing a user to assign system magnification features to the Accessibility button in the navigation bar. -->
<string name="accessibility_magnification_chooser_text">Magnification</string>
+ <!-- Notification title for switching input to the phone's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_phone_mic_notification_title">Switch to phone mic?</string>
+ <!-- Notification title for switching input to the hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_hearing_mic_notification_title">Switch to hearing aid mic?</string>
+ <!-- Notification content for switching input to the phone's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_phone_mic_notification_text">For better sound or if your hearing aid battery is low. This only switches your mic during the call.</string>
+ <!-- Notification content for switching input to the hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_switch_hearing_mic_notification_text">You can use your hearing aid microphone for hands-free calling. This only switches your mic during the call.</string>
+ <!-- Notification action button. Click it will switch the input between phone's microphone and hearing device's microphone. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_notification_switch_button">Switch</string>
+ <!-- Notification action button. Click it will open the bluetooth device details page for this hearing device. It will be shown when making a phone call with the hearing device. [CHAR LIMIT=none] -->
+ <string name="hearing_device_notification_settings_button">Settings</string>
+
<!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
<string name="user_switched">Current user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
<!-- Message shown when switching to a user [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index a18f923d625b..d20b95f59b0c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3167,8 +3167,8 @@
<java-symbol type="integer" name="config_cameraLiftTriggerSensorType" />
<java-symbol type="string" name="config_cameraLiftTriggerSensorStringType" />
<java-symbol type="bool" name="config_cameraDoubleTapPowerGestureEnabled" />
- <java-symbol type="bool" name="config_doubleTapPowerGestureEnabled" />
- <java-symbol type="integer" name="config_defaultDoubleTapPowerGestureAction" />
+ <java-symbol type="integer" name="config_doubleTapPowerGestureMode" />
+ <java-symbol type="integer" name="config_doubleTapPowerGestureMultiTargetDefaultAction" />
<java-symbol type="bool" name="config_emergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureEnabled" />
<java-symbol type="bool" name="config_defaultEmergencyGestureSoundEnabled" />
@@ -3839,6 +3839,13 @@
<java-symbol type="string" name="reduce_bright_colors_feature_name" />
<java-symbol type="string" name="one_handed_mode_feature_name" />
+ <java-symbol type="string" name="hearing_device_switch_phone_mic_notification_title" />
+ <java-symbol type="string" name="hearing_device_switch_hearing_mic_notification_title" />
+ <java-symbol type="string" name="hearing_device_switch_phone_mic_notification_text" />
+ <java-symbol type="string" name="hearing_device_switch_hearing_mic_notification_text" />
+ <java-symbol type="string" name="hearing_device_notification_switch_button" />
+ <java-symbol type="string" name="hearing_device_notification_settings_button" />
+
<!-- com.android.internal.widget.RecyclerView -->
<java-symbol type="id" name="item_touch_helper_previous_elevation"/>
<java-symbol type="dimen" name="item_touch_helper_max_drag_scroll_per_frame"/>
@@ -4025,6 +4032,7 @@
<java-symbol type="string" name="notification_channel_heavy_weight_app" />
<java-symbol type="string" name="notification_channel_system_changes" />
<java-symbol type="string" name="notification_channel_accessibility_magnification" />
+ <java-symbol type="string" name="notification_channel_accessibility_hearing_device" />
<java-symbol type="string" name="notification_channel_accessibility_security_policy" />
<java-symbol type="string" name="notification_channel_display" />
<java-symbol type="string" name="config_defaultAutofillService" />
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 37ef6cba8814..939bf2ec4b0a 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -207,7 +207,8 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
final ComponentInfo info = new ComponentInfo();
info.applicationInfo = new ApplicationInfo();
info.applicationInfo.uid = uid;
- return new RegisteredServicesCache.ServiceInfo<>(type, info, null);
+ return new RegisteredServicesCache.ServiceInfo<>(type, info, null /* componentName */,
+ 0 /* lastUpdateTime */);
}
private void assertNotEmptyFileCreated(TestServicesCache cache, int userId) {
@@ -301,7 +302,7 @@ public class RegisteredServicesCacheTest extends AndroidTestCase {
@Override
protected ServiceInfo<TestServiceType> parseServiceInfo(
- ResolveInfo resolveInfo) throws XmlPullParserException, IOException {
+ ResolveInfo resolveInfo, int userId) throws XmlPullParserException, IOException {
int size = mServices.size();
for (int i = 0; i < size; i++) {
Map<ResolveInfo, ServiceInfo<TestServiceType>> map = mServices.valueAt(i);
diff --git a/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java
new file mode 100644
index 000000000000..ce4aa42f39b6
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/SystemFeaturesCacheTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+import static android.content.pm.PackageManager.FEATURE_WATCH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.util.ArrayMap;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemFeaturesCacheTest {
+
+ private SystemFeaturesCache mCache;
+
+ @Test
+ public void testNoFeatures() throws Exception {
+ SystemFeaturesCache cache = new SystemFeaturesCache(new ArrayMap<String, FeatureInfo>());
+ assertThat(cache.maybeHasFeature("", 0)).isNull();
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse();
+ assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse();
+ assertThat(cache.maybeHasFeature("com.missing.feature", 0)).isNull();
+ }
+
+ @Test
+ public void testNonSdkFeature() throws Exception {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
+ features.put("custom.feature", createFeature("custom.feature", 0));
+ SystemFeaturesCache cache = new SystemFeaturesCache(features);
+
+ assertThat(cache.maybeHasFeature("custom.feature", 0)).isNull();
+ }
+
+ @Test
+ public void testSdkFeature() throws Exception {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
+ features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0));
+ SystemFeaturesCache cache = new SystemFeaturesCache(features);
+
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isTrue();
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, -1)).isTrue();
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, 1)).isFalse();
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isTrue();
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MAX_VALUE)).isFalse();
+
+ // Other SDK-declared features should be reported as unavailable.
+ assertThat(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0)).isFalse();
+ }
+
+ @Test
+ public void testSdkFeatureHasMinVersion() throws Exception {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
+ features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, Integer.MIN_VALUE));
+ SystemFeaturesCache cache = new SystemFeaturesCache(features);
+
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, 0)).isFalse();
+
+ // If both the query and the feature version itself happen to use MIN_VALUE, we can't
+ // reliably indicate availability, so it should report an indeterminate result.
+ assertThat(cache.maybeHasFeature(FEATURE_WATCH, Integer.MIN_VALUE)).isNull();
+ }
+
+ @Test
+ public void testParcel() throws Exception {
+ ArrayMap<String, FeatureInfo> features = new ArrayMap<>();
+ features.put(FEATURE_WATCH, createFeature(FEATURE_WATCH, 0));
+ SystemFeaturesCache cache = new SystemFeaturesCache(features);
+
+ Parcel parcel = Parcel.obtain();
+ SystemFeaturesCache parceledCache;
+ try {
+ parcel.writeParcelable(cache, 0);
+ parcel.setDataPosition(0);
+ parceledCache = parcel.readParcelable(getClass().getClassLoader());
+ } finally {
+ parcel.recycle();
+ }
+
+ assertThat(parceledCache.maybeHasFeature(FEATURE_WATCH, 0))
+ .isEqualTo(cache.maybeHasFeature(FEATURE_WATCH, 0));
+ assertThat(parceledCache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0))
+ .isEqualTo(cache.maybeHasFeature(FEATURE_PICTURE_IN_PICTURE, 0));
+ assertThat(parceledCache.maybeHasFeature("custom.feature", 0))
+ .isEqualTo(cache.maybeHasFeature("custom.feature", 0));
+ }
+
+ private static FeatureInfo createFeature(String name, int version) {
+ FeatureInfo fi = new FeatureInfo();
+ fi.name = name;
+ fi.version = version;
+ return fi;
+ }
+}
diff --git a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
index 0bf406c970f2..2bd3f4df9435 100644
--- a/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
+++ b/core/tests/coretests/src/com/android/internal/notification/SystemNotificationChannelsTest.java
@@ -17,6 +17,7 @@
package com.android.internal.notification;
import static com.android.internal.notification.SystemNotificationChannels.ABUSIVE_BACKGROUND_APPS;
+import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_HEARING_DEVICE;
import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_MAGNIFICATION;
import static com.android.internal.notification.SystemNotificationChannels.ACCESSIBILITY_SECURITY_POLICY;
import static com.android.internal.notification.SystemNotificationChannels.ACCOUNT;
@@ -90,8 +91,8 @@ public class SystemNotificationChannelsTest {
DEVELOPER_IMPORTANT, UPDATES, NETWORK_STATUS, NETWORK_ALERTS,
NETWORK_AVAILABLE, VPN, DEVICE_ADMIN, ALERTS, RETAIL_MODE, USB,
FOREGROUND_SERVICE, HEAVY_WEIGHT_APP, SYSTEM_CHANGES,
- ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_SECURITY_POLICY,
- ABUSIVE_BACKGROUND_APPS);
+ ACCESSIBILITY_MAGNIFICATION, ACCESSIBILITY_HEARING_DEVICE,
+ ACCESSIBILITY_SECURITY_POLICY, ABUSIVE_BACKGROUND_APPS);
}
@Test
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 4c75ea4777da..957d1b835ec2 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -26,8 +26,8 @@ package {
java_library {
name: "wm_shell_protolog-groups",
srcs: [
- "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
":protolog-common-src",
+ "src/com/android/wm/shell/protolog/ShellProtoLogGroup.java",
],
}
@@ -61,8 +61,8 @@ java_genrule {
name: "wm_shell_protolog_src",
srcs: [
":protolog-impl",
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) transform-protolog-calls " +
@@ -80,8 +80,8 @@ java_genrule {
java_genrule {
name: "generate-wm_shell_protolog.json",
srcs: [
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
@@ -97,8 +97,8 @@ java_genrule {
java_genrule {
name: "gen-wmshell.protolog.pb",
srcs: [
- ":wm_shell_protolog-groups",
":wm_shell-sources",
+ ":wm_shell_protolog-groups",
],
tools: ["protologtool"],
cmd: "$(location protologtool) generate-viewer-config " +
@@ -159,38 +159,39 @@ java_library {
android_library {
name: "WindowManager-Shell",
srcs: [
- "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell_protolog_src",
// TODO(b/168581922) protologtool do not support kotlin(*.kt)
- ":wm_shell-sources-kt",
+ "src/com/android/wm/shell/EventLogTags.logtags",
":wm_shell-aidls",
":wm_shell-shared-aidls",
+ ":wm_shell-sources-kt",
],
resource_dirs: [
"res",
],
static_libs: [
+ "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
+ "//frameworks/libs/systemui:iconloader_base",
+ "//packages/apps/Car/SystemUI/aconfig:com_android_systemui_car_flags_lib",
+ "PlatformAnimationLib",
+ "WindowManager-Shell-lite-proto",
+ "WindowManager-Shell-proto",
+ "WindowManager-Shell-shared",
+ "androidx-constraintlayout_constraintlayout",
"androidx.appcompat_appcompat",
- "androidx.core_core-ktx",
"androidx.arch.core_core-runtime",
- "androidx.datastore_datastore",
"androidx.compose.material3_material3",
- "androidx-constraintlayout_constraintlayout",
+ "androidx.core_core-ktx",
+ "androidx.datastore_datastore",
"androidx.dynamicanimation_dynamicanimation",
"androidx.recyclerview_recyclerview",
- "kotlinx-coroutines-android",
- "kotlinx-coroutines-core",
- "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib",
- "//frameworks/libs/systemui:iconloader_base",
"com_android_launcher3_flags_lib",
"com_android_wm_shell_flags_lib",
- "PlatformAnimationLib",
- "WindowManager-Shell-proto",
- "WindowManager-Shell-lite-proto",
- "WindowManager-Shell-shared",
- "perfetto_trace_java_protos",
"dagger2",
"jsr330",
+ "kotlinx-coroutines-android",
+ "kotlinx-coroutines-core",
+ "perfetto_trace_java_protos",
],
libs: [
// Soong fails to automatically add this dependency because all the
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
index 9f01316d5b5c..b098620fde2e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootTaskDisplayAreaOrganizer.java
@@ -230,6 +230,11 @@ public class RootTaskDisplayAreaOrganizer extends DisplayAreaOrganizer {
return mDisplayAreasInfo.get(displayId);
}
+ @Nullable
+ public SurfaceControl getDisplayAreaLeash(int displayId) {
+ return mLeashes.get(displayId);
+ }
+
/**
* Applies the {@link DisplayAreaInfo} to the {@link DisplayAreaContext} specified by
* {@link DisplayAreaInfo#displayId}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
index 06a55d3dbbd0..08079d94fcee 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/AppToWebUtils.kt
@@ -19,7 +19,6 @@
package com.android.wm.shell.apptoweb
import android.app.assist.AssistContent
-import android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI
import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_VIEW
@@ -113,5 +112,5 @@ fun getDomainVerificationUserState(
* Returns the web uri from the given [AssistContent].
*/
fun AssistContent.getSessionWebUri(): Uri? {
- return extras.getParcelable(EXTRA_SESSION_TRANSFER_WEB_URI) ?: webUri
+ return sessionTransferUri ?: webUri
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
index 4cc81a9e6f8f..ec3637aacf91 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/apptoweb/OpenByDefaultDialog.kt
@@ -18,9 +18,11 @@ package com.android.wm.shell.apptoweb
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
+import android.content.pm.PackageManager.NameNotFoundException
import android.content.pm.verify.domain.DomainVerificationManager
import android.graphics.Bitmap
import android.graphics.PixelFormat
+import android.util.Slog
import android.view.LayoutInflater
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
@@ -160,8 +162,15 @@ internal class OpenByDefaultDialog(
}
private fun setDefaultLinkHandlingSetting() {
- domainVerificationManager.setDomainVerificationLinkHandlingAllowed(
- packageName, openInAppButton.isChecked)
+ try {
+ domainVerificationManager.setDomainVerificationLinkHandlingAllowed(
+ packageName, openInAppButton.isChecked)
+ } catch (e: NameNotFoundException) {
+ Slog.e(
+ TAG,
+ "Failed to change link handling policy due to the package name is not found: " + e
+ )
+ }
}
private fun closeMenu() {
@@ -203,4 +212,8 @@ internal class OpenByDefaultDialog(
/** Called when open by default dialog view has been released. */
fun onDialogDismissed()
}
+
+ companion object {
+ private const val TAG = "OpenByDefaultDialog"
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java
new file mode 100644
index 000000000000..fc51c754e06b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoShellModule.java
@@ -0,0 +1,30 @@
+/*
+ * 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.automotive;
+
+import com.android.wm.shell.dagger.WMSingleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+
+@Module
+public abstract class AutoShellModule {
+ @WMSingleton
+ @Binds
+ abstract AutoTaskStackController provideTaskStackController(AutoTaskStackControllerImpl impl);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt
new file mode 100644
index 000000000000..caacdd355996
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStack.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.automotive
+
+import android.app.ActivityManager
+import android.graphics.Rect
+import android.view.SurfaceControl
+
+/**
+ * Represents an auto task stack, which is always in multi-window mode.
+ *
+ * @property id The ID of the task stack.
+ * @property displayId The ID of the display the task stack is on.
+ * @property leash The surface control leash of the task stack.
+ */
+interface AutoTaskStack {
+ val id: Int
+ val displayId: Int
+ var leash: SurfaceControl
+}
+
+/**
+ * Data class representing the state of an auto task stack.
+ *
+ * @property bounds The bounds of the task stack.
+ * @property childrenTasksVisible Whether the child tasks of the stack are visible.
+ * @property layer The layer of the task stack.
+ */
+data class AutoTaskStackState(
+ val bounds: Rect = Rect(),
+ val childrenTasksVisible: Boolean,
+ val layer: Int
+)
+
+/**
+ * Data class representing a root task stack.
+ *
+ * @property id The ID of the root task stack
+ * @property displayId The ID of the display the root task stack is on.
+ * @property leash The surface control leash of the root task stack.
+ * @property rootTaskInfo The running task info of the root task.
+ */
+data class RootTaskStack(
+ override val id: Int,
+ override val displayId: Int,
+ override var leash: SurfaceControl,
+ var rootTaskInfo: ActivityManager.RunningTaskInfo
+) : AutoTaskStack
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt
new file mode 100644
index 000000000000..15fedac62af3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackController.kt
@@ -0,0 +1,229 @@
+/*
+ * 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.automotive
+
+import android.app.PendingIntent
+import android.content.Intent
+import android.os.Bundle
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+
+/**
+ * Delegate interface for handling auto task stack transitions.
+ */
+interface AutoTaskStackTransitionHandlerDelegate {
+ /**
+ * Handles a transition request.
+ *
+ * @param transition The transition identifier.
+ * @param request The transition request information.
+ * @return An [AutoTaskStackTransaction] to be applied for the transition, or null if the
+ * animation is not handled by this delegate.
+ */
+ fun handleRequest(
+ transition: IBinder, request: TransitionRequestInfo
+ ): AutoTaskStackTransaction?
+
+ /**
+ * See [Transitions.TransitionHandler.startAnimation] for more details.
+ *
+ * @param changedTaskStacks Contains the states of the task stacks that were changed as a
+ * result of this transition. The key is the [AutoTaskStack.id] and the value is the
+ * corresponding [AutoTaskStackState].
+ */
+ fun startAnimation(
+ transition: IBinder,
+ changedTaskStacks: Map<Int, AutoTaskStackState>,
+ info: TransitionInfo,
+ startTransaction: SurfaceControl.Transaction,
+ finishTransaction: SurfaceControl.Transaction,
+ finishCallback: TransitionFinishCallback
+ ): Boolean
+
+ /**
+ * See [Transitions.TransitionHandler.onTransitionConsumed] for more details.
+ *
+ * @param requestedTaskStacks contains the states of the task stacks that were requested in
+ * the transition. The key is the [AutoTaskStack.id] and the value is the corresponding
+ * [AutoTaskStackState].
+ */
+ fun onTransitionConsumed(
+ transition: IBinder,
+ requestedTaskStacks: Map<Int, AutoTaskStackState>,
+ aborted: Boolean, finishTransaction: SurfaceControl.Transaction?
+ )
+
+ /**
+ * See [Transitions.TransitionHandler.mergeAnimation] for more details.
+ *
+ * @param changedTaskStacks Contains the states of the task stacks that were changed as a
+ * result of this transition. The key is the [AutoTaskStack.id] and the value is the
+ * corresponding [AutoTaskStackState].
+ */
+ fun mergeAnimation(
+ transition: IBinder,
+ changedTaskStacks: Map<Int, AutoTaskStackState>,
+ info: TransitionInfo,
+ surfaceTransaction: SurfaceControl.Transaction,
+ mergeTarget: IBinder,
+ finishCallback: TransitionFinishCallback
+ )
+}
+
+
+/**
+ * Controller for managing auto task stacks.
+ */
+interface AutoTaskStackController {
+
+ var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate?
+ set
+
+ /**
+ * Map of task stack IDs to their states.
+ *
+ * This gets updated right before [AutoTaskStackTransitionHandlerDelegate.startAnimation] or
+ * [AutoTaskStackTransitionHandlerDelegate.onTransitionConsumed] is called.
+ */
+ val taskStackStateMap: Map<Int, AutoTaskStackState>
+ get
+
+ /**
+ * Creates a new multi-window root task.
+ *
+ * A root task stack is placed in the default TDA of the specified display by default.
+ * Once the root task is removed, the [AutoTaskStackController] no longer holds a reference to
+ * it.
+ *
+ * @param displayId The ID of the display to create the root task stack on.
+ * @param listener The listener for root task stack events.
+ */
+ @ShellMainThread
+ fun createRootTaskStack(displayId: Int, listener: RootTaskStackListener)
+
+
+ /**
+ * Sets the default root task stack (launch root) on a display. Calling it again with a
+ * different [rootTaskStackId] will simply replace the default root task stack on the display.
+ *
+ * Note: This is helpful for passively routing tasks to a specified container. If a display
+ * doesn't have a default root task stack set, all tasks will open in fullscreen and cover
+ * the entire default TDA by default.
+ *
+ * @param displayId The ID of the display.
+ * @param rootTaskStackId The ID of the root task stack, or null to clear the default.
+ */
+ @ShellMainThread
+ fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?)
+
+ /**
+ * Starts a transaction with the specified [transaction].
+ * Returns the transition identifier.
+ */
+ @ShellMainThread
+ fun startTransition(transaction: AutoTaskStackTransaction): IBinder?
+}
+
+internal sealed class TaskStackOperation {
+ data class ReparentTask(
+ val taskId: Int,
+ val parentTaskStackId: Int,
+ val onTop: Boolean
+ ) : TaskStackOperation()
+
+ data class SendPendingIntent(
+ val sender: PendingIntent,
+ val intent: Intent,
+ val options: Bundle?
+ ) : TaskStackOperation()
+
+ data class SetTaskStackState(
+ val taskStackId: Int,
+ val state: AutoTaskStackState
+ ) : TaskStackOperation()
+}
+
+data class AutoTaskStackTransaction internal constructor(
+ internal val operations: MutableList<TaskStackOperation> = mutableListOf()
+) {
+ constructor() : this(
+ mutableListOf()
+ )
+
+ /** See [WindowContainerTransaction.reparent] for more details. */
+ fun reparentTask(
+ taskId: Int,
+ parentTaskStackId: Int,
+ onTop: Boolean
+ ): AutoTaskStackTransaction {
+ operations.add(TaskStackOperation.ReparentTask(taskId, parentTaskStackId, onTop))
+ return this
+ }
+
+ /** See [WindowContainerTransaction.sendPendingIntent] for more details. */
+ fun sendPendingIntent(
+ sender: PendingIntent,
+ intent: Intent,
+ options: Bundle?
+ ): AutoTaskStackTransaction {
+ operations.add(TaskStackOperation.SendPendingIntent(sender, intent, options))
+ return this
+ }
+
+ /**
+ * Adds a set task stack state operation to the transaction.
+ *
+ * If an operation with the same task stack ID already exists, it is replaced with the new one.
+ *
+ * @param taskStackId The ID of the task stack.
+ * @param state The new state of the task stack.
+ * @return The transaction with the added operation.
+ */
+ fun setTaskStackState(taskStackId: Int, state: AutoTaskStackState): AutoTaskStackTransaction {
+ val existingOperation = operations.find {
+ it is TaskStackOperation.SetTaskStackState && it.taskStackId == taskStackId
+ }
+ if (existingOperation != null) {
+ val index = operations.indexOf(existingOperation)
+ operations[index] = TaskStackOperation.SetTaskStackState(taskStackId, state)
+ } else {
+ operations.add(TaskStackOperation.SetTaskStackState(taskStackId, state))
+ }
+ return this
+ }
+
+ /**
+ * Returns a map of task stack IDs to their states from the set task stack state operations.
+ *
+ * @return The map of task stack IDs to states.
+ */
+ fun getTaskStackStates(): Map<Int, AutoTaskStackState> {
+ val states = mutableMapOf<Int, AutoTaskStackState>()
+ operations.forEach { operation ->
+ if (operation is TaskStackOperation.SetTaskStackState) {
+ states[operation.taskStackId] = operation.state
+ }
+ }
+ return states
+ }
+}
+
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
new file mode 100644
index 000000000000..f8f284238a98
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/AutoTaskStackControllerImpl.kt
@@ -0,0 +1,534 @@
+/*
+ * 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.automotive
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT
+import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
+import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.os.IBinder
+import android.util.Log
+import android.util.Slog
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.systemui.car.Flags.autoTaskStackWindowing
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTaskOrganizer
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.dagger.WMSingleton
+import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+import javax.inject.Inject
+
+const val TAG = "AutoTaskStackController"
+
+@WMSingleton
+class AutoTaskStackControllerImpl @Inject constructor(
+ val taskOrganizer: ShellTaskOrganizer,
+ @ShellMainThread private val shellMainThread: ShellExecutor,
+ val transitions: Transitions,
+ val shellInit: ShellInit,
+ val rootTdaOrganizer: RootTaskDisplayAreaOrganizer
+) : AutoTaskStackController, Transitions.TransitionHandler {
+ override var autoTransitionHandlerDelegate: AutoTaskStackTransitionHandlerDelegate? = null
+ override val taskStackStateMap = mutableMapOf<Int, AutoTaskStackState>()
+
+ private val DBG = Log.isLoggable(TAG, Log.DEBUG)
+ private val taskStackMap = mutableMapOf<Int, AutoTaskStack>()
+ private val pendingTransitions = ArrayList<PendingTransition>()
+ private val mTaskStackStateTranslator = TaskStackStateTranslator()
+ private val appTasksMap = mutableMapOf<Int, ActivityManager.RunningTaskInfo>()
+ private val defaultRootTaskPerDisplay = mutableMapOf<Int, Int>()
+
+ init {
+ if (!autoTaskStackWindowing()) {
+ throw IllegalStateException("Failed to initialize" +
+ "AutoTaskStackController as the auto_task_stack_windowing TS flag is disabled.")
+ } else {
+ shellInit.addInitCallback(this::onInit, this);
+ }
+ }
+
+ fun onInit() {
+ transitions.addHandler(this)
+ }
+
+ /** Translates the [AutoTaskStackState] to relevant WM and surface transactions. */
+ inner class TaskStackStateTranslator {
+ // TODO(b/384946072): Move to an interface with 2 implementations, one for root task and
+ // other for TDA
+ fun applyVisibilityAndBounds(
+ wct: WindowContainerTransaction,
+ taskStack: AutoTaskStack,
+ state: AutoTaskStackState
+ ) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to convertToWct")
+ return
+ }
+ wct.setBounds(taskStack.rootTaskInfo.token, state.bounds)
+ wct.reorder(taskStack.rootTaskInfo.token, /* onTop= */ state.childrenTasksVisible)
+ }
+
+ fun reorderLeash(
+ taskStack: AutoTaskStack,
+ state: AutoTaskStackState,
+ transaction: Transaction
+ ) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to reorder leash")
+ return
+ }
+ Slog.d(TAG, "Setting the layer ${state.layer}")
+ transaction.setLayer(taskStack.leash, state.layer)
+ }
+
+ fun restoreLeash(taskStack: AutoTaskStack, transaction: Transaction) {
+ if (taskStack !is RootTaskStack) {
+ Slog.e(TAG, "Unsupported task stack, unable to restore leash")
+ return
+ }
+
+ val rootTdaInfo = rootTdaOrganizer.getDisplayAreaInfo(taskStack.displayId)
+ if (rootTdaInfo == null ||
+ rootTdaInfo.featureId != taskStack.rootTaskInfo.displayAreaFeatureId
+ ) {
+ Slog.e(TAG, "Cannot find the rootTDA for the root task stack ${taskStack.id}")
+ return
+ }
+ if (DBG) {
+ Slog.d(TAG, "Reparenting ${taskStack.id} leash to DA ${rootTdaInfo.featureId}")
+ }
+ transaction.reparent(
+ taskStack.leash,
+ rootTdaOrganizer.getDisplayAreaLeash(taskStack.displayId)
+ )
+ }
+ }
+
+ inner class RootTaskStackListenerAdapter(
+ val rootTaskStackListener: RootTaskStackListener,
+ ) : ShellTaskOrganizer.TaskListener {
+ private var rootTaskStack: RootTaskStack? = null
+
+ // TODO(b/384948029): Notify car service for all the children tasks' events
+ override fun onTaskAppeared(
+ taskInfo: ActivityManager.RunningTaskInfo?,
+ leash: SurfaceControl?
+ ) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskAppeared")
+ }
+ if (leash == null) {
+ throw IllegalArgumentException("leash can't be null in onTaskAppeared")
+ }
+ if (DBG) Slog.d(TAG, "onTaskAppeared = ${taskInfo.taskId}")
+
+ if (rootTaskStack == null) {
+ val rootTask =
+ RootTaskStack(taskInfo.taskId, taskInfo.displayId, leash, taskInfo)
+ taskStackMap[rootTask.id] = rootTask
+
+ rootTaskStack = rootTask;
+ rootTaskStackListener.onRootTaskStackCreated(rootTask);
+ return
+ }
+ appTasksMap[taskInfo.taskId] = taskInfo
+ rootTaskStackListener.onTaskAppeared(taskInfo, leash)
+ }
+
+ override fun onTaskInfoChanged(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskInfoChanged")
+ }
+ if (DBG) Slog.d(TAG, "onTaskInfoChanged = ${taskInfo.taskId}")
+ var previousRootTaskStackInfo = rootTaskStack ?: run {
+ Slog.e(TAG, "Received onTaskInfoChanged, when root task stack is null")
+ return@onTaskInfoChanged
+ }
+ rootTaskStack?.let {
+ if (taskInfo.taskId == previousRootTaskStackInfo.id) {
+ previousRootTaskStackInfo = previousRootTaskStackInfo.copy(rootTaskInfo = taskInfo)
+ taskStackMap[previousRootTaskStackInfo.id] = previousRootTaskStackInfo
+ rootTaskStack = previousRootTaskStackInfo;
+ rootTaskStackListener.onRootTaskStackInfoChanged(it)
+ return
+ }
+ }
+
+ appTasksMap[taskInfo.taskId] = taskInfo
+ rootTaskStackListener.onTaskInfoChanged(taskInfo)
+ }
+
+ override fun onTaskVanished(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onTaskVanished")
+ }
+ if (DBG) Slog.d(TAG, "onTaskVanished = ${taskInfo.taskId}")
+ var rootTask = rootTaskStack ?: run {
+ Slog.e(TAG, "Received onTaskVanished, when root task stack is null")
+ return@onTaskVanished
+ }
+ if (taskInfo.taskId == rootTask.id) {
+ rootTask = rootTask.copy(rootTaskInfo = taskInfo)
+ rootTaskStack = rootTask
+ rootTaskStackListener.onRootTaskStackDestroyed(rootTask)
+ taskStackMap.remove(rootTask.id)
+ taskStackStateMap.remove(rootTask.id)
+ rootTaskStack = null
+ return
+ }
+ appTasksMap.remove(taskInfo.taskId)
+ rootTaskStackListener.onTaskVanished(taskInfo)
+ }
+
+ override fun onBackPressedOnTaskRoot(taskInfo: ActivityManager.RunningTaskInfo?) {
+ if (taskInfo == null) {
+ throw IllegalArgumentException("taskInfo can't be null in onBackPressedOnTaskRoot")
+ }
+ super.onBackPressedOnTaskRoot(taskInfo)
+ rootTaskStackListener.onBackPressedOnTaskRoot(taskInfo)
+ }
+ }
+
+ override fun createRootTaskStack(
+ displayId: Int,
+ listener: RootTaskStackListener
+ ) {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to create root task stack as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return
+ }
+ taskOrganizer.createRootTask(
+ displayId,
+ WINDOWING_MODE_MULTI_WINDOW,
+ RootTaskStackListenerAdapter(listener),
+ /* removeWithTaskOrganizer= */ true
+ )
+ }
+
+ override fun setDefaultRootTaskStackOnDisplay(displayId: Int, rootTaskStackId: Int?) {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to set default root task stack as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return
+ }
+ var wct = WindowContainerTransaction()
+
+ // Clear the default root task stack if already set
+ defaultRootTaskPerDisplay[displayId]?.let { existingDefaultRootTaskStackId ->
+ (taskStackMap[existingDefaultRootTaskStackId] as? RootTaskStack)?.let { rootTaskStack ->
+ wct.setLaunchRoot(rootTaskStack.rootTaskInfo.token, null, null)
+ }
+ }
+
+ if (rootTaskStackId != null) {
+ var taskStack =
+ taskStackMap[rootTaskStackId] ?: run { return@setDefaultRootTaskStackOnDisplay }
+ if (DBG) Slog.d(TAG, "setting launch root for = ${taskStack.id}")
+ if (taskStack !is RootTaskStack) {
+ throw IllegalArgumentException(
+ "Cannot set a non root task stack as default root task " +
+ "stack"
+ )
+ }
+ wct.setLaunchRoot(
+ taskStack.rootTaskInfo.token,
+ intArrayOf(WINDOWING_MODE_UNDEFINED),
+ intArrayOf(
+ ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_UNDEFINED, ACTIVITY_TYPE_RECENTS,
+
+ // TODO(b/386242708): Figure out if this flag will ever be used for automotive
+ // assistant. Based on output, remove it from here and fix the
+ // AssistantStackTests accordingly.
+ ACTIVITY_TYPE_ASSISTANT
+ )
+ )
+ }
+
+ taskOrganizer.applyTransaction(wct)
+ }
+
+ override fun startTransition(transaction: AutoTaskStackTransaction): IBinder? {
+ if (!autoTaskStackWindowing()) {
+ Slog.e(
+ TAG, "Failed to start transaction as the " +
+ "auto_task_stack_windowing TS flag is disabled."
+ )
+ return null
+ }
+ if (transaction.operations.isEmpty()) {
+ Slog.e(TAG, "Operations empty, no transaction started")
+ return null
+ }
+ if (DBG) Slog.d(TAG, "startTransaction ${transaction.operations}")
+
+ var wct = WindowContainerTransaction()
+ convertToWct(transaction, wct)
+ var pending = PendingTransition(
+ TRANSIT_CHANGE,
+ wct,
+ transaction,
+ )
+ return startTransitionNow(pending)
+ }
+
+ override fun handleRequest(
+ transition: IBinder,
+ request: TransitionRequestInfo
+ ): WindowContainerTransaction? {
+ if (DBG) {
+ Slog.d(TAG, "handle request, id=${request.debugId}, type=${request.type}, " +
+ "triggertask = ${request.triggerTask ?: "null"}")
+ }
+ val ast = autoTransitionHandlerDelegate?.handleRequest(transition, request)
+ ?: run { return@handleRequest null }
+
+ if (ast.operations.isEmpty()) {
+ return null
+ }
+ var wct = WindowContainerTransaction()
+ convertToWct(ast, wct)
+
+ pendingTransitions.add(
+ PendingTransition(request.type, wct, ast).apply { isClaimed = transition }
+ )
+ return wct
+ }
+
+ fun updateTaskStackStates(taskStatStates: Map<Int, AutoTaskStackState>) {
+ taskStackStateMap.putAll(taskStatStates)
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startTransaction: Transaction,
+ finishTransaction: Transaction,
+ finishCallback: TransitionFinishCallback
+ ): Boolean {
+ if (DBG) Slog.d(TAG, " startAnimation, id=${info.debugId} = changes=" + info.changes)
+ val pending: PendingTransition? = findPending(transition)
+ if (pending != null) {
+ pendingTransitions.remove(pending)
+ updateTaskStackStates(pending.transaction.getTaskStackStates())
+ }
+
+ reorderLeashes(startTransaction)
+ reorderLeashes(finishTransaction)
+
+ for (chg in info.changes) {
+ // TODO(b/384946072): handle the da stack similarly. The below implementation only
+ // handles the root task stack
+
+ val taskInfo = chg.taskInfo ?: continue
+ val taskStack = taskStackMap[taskInfo.taskId] ?: continue
+
+ // Restore the leashes for the task stacks to ensure correct z-order competition
+ if (taskStackMap.containsKey(taskInfo.taskId)) {
+ mTaskStackStateTranslator.restoreLeash(
+ taskStack,
+ startTransaction
+ )
+ if (TransitionUtil.isOpeningMode(chg.mode)) {
+ // Clients can still manipulate the alpha, but this ensures that the default
+ // behavior is natural
+ startTransaction.setAlpha(chg.leash, 1f)
+ }
+ continue
+ }
+ }
+
+ val isPlayedByDelegate = autoTransitionHandlerDelegate?.startAnimation(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ info,
+ startTransaction,
+ finishTransaction,
+ {
+ shellMainThread.execute {
+ finishCallback.onTransitionFinished(it)
+ startNextTransition()
+ }
+ }
+ ) ?: false
+
+ if (isPlayedByDelegate) {
+ if (DBG) Slog.d(TAG, "${info.debugId} played");
+ return true;
+ }
+
+ // If for an animation which is not played by the delegate, contains a change in a known
+ // task stack, it should be leveraged to correct the leashes. So, handle the animation in
+ // this case.
+ if (info.changes.any { taskStackMap.containsKey(it.taskInfo?.taskId) }) {
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(null)
+ startNextTransition()
+ if (DBG) Slog.d(TAG, "${info.debugId} played");
+ return true
+ }
+ return false;
+ }
+
+ fun convertToWct(ast: AutoTaskStackTransaction, wct: WindowContainerTransaction) {
+ ast.operations.forEach { operation ->
+ when (operation) {
+ is TaskStackOperation.ReparentTask -> {
+ val appTask = appTasksMap[operation.taskId]
+
+ if (appTask == null) {
+ Slog.e(
+ TAG, "task with id=$operation.taskId not found, failed to " +
+ "reparent."
+ )
+ return@forEach
+ }
+ if (!taskStackMap.containsKey(operation.parentTaskStackId)) {
+ Slog.e(
+ TAG, "task stack with id=${operation.parentTaskStackId} not " +
+ "found, failed to reparent"
+ )
+ return@forEach
+ }
+ // TODO(b/384946072): Handle a display area stack as well
+ wct.reparent(
+ appTask.token,
+ (taskStackMap[operation.parentTaskStackId] as RootTaskStack)
+ .rootTaskInfo.token,
+ operation.onTop
+ )
+ }
+
+ is TaskStackOperation.SendPendingIntent -> wct.sendPendingIntent(
+ operation.sender,
+ operation.intent,
+ operation.options
+ )
+
+ is TaskStackOperation.SetTaskStackState -> {
+ taskStackMap[operation.taskStackId]?.let { taskStack ->
+ mTaskStackStateTranslator.applyVisibilityAndBounds(
+ wct,
+ taskStack,
+ operation.state
+ )
+ }
+ ?: Slog.w(TAG, "AutoTaskStack with id ${operation.taskStackId} " +
+ "not found.")
+ }
+ }
+ }
+ }
+
+ override fun mergeAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ surfaceTransaction: Transaction,
+ mergeTarget: IBinder,
+ finishCallback: TransitionFinishCallback
+ ) {
+ val pending: PendingTransition? = findPending(transition)
+
+ autoTransitionHandlerDelegate?.mergeAnimation(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ info,
+ surfaceTransaction,
+ mergeTarget,
+ /* finishCallback = */ {
+ shellMainThread.execute {
+ finishCallback.onTransitionFinished(it)
+ }
+ }
+ )
+ }
+
+ override fun onTransitionConsumed(
+ transition: IBinder,
+ aborted: Boolean,
+ finishTransaction: Transaction?
+ ) {
+ val pending: PendingTransition? = findPending(transition)
+ if (pending != null) {
+ pendingTransitions.remove(pending)
+ updateTaskStackStates(pending.transaction.getTaskStackStates())
+ // Still update the surface order because this means wm didn't lead to any change
+ if (finishTransaction != null) {
+ reorderLeashes(finishTransaction)
+ }
+ }
+ autoTransitionHandlerDelegate?.onTransitionConsumed(
+ transition,
+ pending?.transaction?.getTaskStackStates() ?: mapOf(),
+ aborted,
+ finishTransaction
+ )
+ }
+
+ private fun reorderLeashes(transaction: SurfaceControl.Transaction) {
+ taskStackStateMap.forEach { (taskId, taskStackState) ->
+ taskStackMap[taskId]?.let { taskStack ->
+ mTaskStackStateTranslator.reorderLeash(taskStack, taskStackState, transaction)
+ } ?: Slog.w(TAG, "Warning: AutoTaskStack with id $taskId not found.")
+ }
+ }
+
+ private fun findPending(claimed: IBinder) = pendingTransitions.find { it.isClaimed == claimed }
+
+ private fun startTransitionNow(pending: PendingTransition): IBinder {
+ val claimedTransition = transitions.startTransition(pending.mType, pending.wct, this)
+ pending.isClaimed = claimedTransition
+ pendingTransitions.add(pending)
+ return claimedTransition
+ }
+
+ fun startNextTransition() {
+ if (pendingTransitions.isEmpty()) return
+ val pending: PendingTransition = pendingTransitions[0]
+ if (pending.isClaimed != null) {
+ // Wait for this to start animating.
+ return
+ }
+ pending.isClaimed = transitions.startTransition(pending.mType, pending.wct, this)
+ }
+
+ internal class PendingTransition(
+ @field:WindowManager.TransitionType @param:WindowManager.TransitionType val mType: Int,
+ val wct: WindowContainerTransaction,
+ val transaction: AutoTaskStackTransaction,
+ ) {
+ var isClaimed: IBinder? = null
+ }
+
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt
new file mode 100644
index 000000000000..9d121b144492
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/automotive/RootTaskStackListener.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.automotive
+
+import com.android.wm.shell.ShellTaskOrganizer
+
+/**
+ * A [TaskListener] which simplifies the interface when used for
+ * [ShellTaskOrganizer.createRootTask].
+ *
+ * [onRootTaskStackCreated], [onRootTaskStackInfoChanged], [onRootTaskStackDestroyed] will be called
+ * for the underlying root task.
+ * The [onTaskAppeared], [onTaskInfoChanged], [onTaskVanished] are called for the children tasks.
+ */
+interface RootTaskStackListener : ShellTaskOrganizer.TaskListener {
+ fun onRootTaskStackCreated(rootTaskStack: RootTaskStack)
+ fun onRootTaskStackInfoChanged(rootTaskStack: RootTaskStack)
+ fun onRootTaskStackDestroyed(rootTaskStack: RootTaskStack)
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index e96bc02c1198..8dabd54a01ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -28,7 +28,6 @@ import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.window.flags.Flags.unifyBackNavigationTransition;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
@@ -40,23 +39,17 @@ import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.TaskInfo;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Configuration;
-import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.os.UserHandle;
-import android.provider.Settings.Global;
import android.util.Log;
import android.view.IRemoteAnimationRunner;
import android.view.InputDevice;
@@ -92,7 +85,6 @@ import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.shared.TransitionUtil;
-import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ConfigurationChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -102,7 +94,6 @@ import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
/**
@@ -111,14 +102,7 @@ import java.util.function.Predicate;
public class BackAnimationController implements RemoteCallable<BackAnimationController>,
ConfigurationChangeListener {
private static final String TAG = "ShellBackPreview";
- private static final int SETTING_VALUE_OFF = 0;
- private static final int SETTING_VALUE_ON = 1;
- public static final boolean IS_ENABLED =
- SystemProperties.getInt("persist.wm.debug.predictive_back",
- SETTING_VALUE_ON) == SETTING_VALUE_ON;
-
- /** Predictive back animation developer option */
- private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
+
/**
* Max duration to wait for an animation to finish before triggering the real back.
*/
@@ -148,11 +132,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private boolean mReceivedNullNavigationInfo = false;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
- private final ContentResolver mContentResolver;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
private final ShellExecutor mShellExecutor;
- private final Handler mBgHandler;
private final WindowManager mWindowManager;
private final Transitions mTransitions;
@VisibleForTesting
@@ -245,7 +227,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
- @NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
@@ -256,10 +237,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
shellInit,
shellController,
shellExecutor,
- backgroundHandler,
ActivityTaskManager.getService(),
context,
- context.getContentResolver(),
backAnimationBackground,
shellBackAnimationRegistry,
shellCommandHandler,
@@ -272,10 +251,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull ShellInit shellInit,
@NonNull ShellController shellController,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
- @NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull IActivityTaskManager activityTaskManager,
Context context,
- ContentResolver contentResolver,
@NonNull BackAnimationBackground backAnimationBackground,
ShellBackAnimationRegistry shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
@@ -285,10 +262,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor = shellExecutor;
mActivityTaskManager = activityTaskManager;
mContext = context;
- mContentResolver = contentResolver;
mRequirePointerPilfer =
context.getResources().getBoolean(R.bool.config_backAnimationRequiresPointerPilfer);
- mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mAnimationBackground = backAnimationBackground;
mShellBackAnimationRegistry = shellBackAnimationRegistry;
@@ -305,8 +280,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void onInit() {
- setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
- updateEnableAnimationFromFlags();
createAdapter();
mShellController.addExternalInterface(IBackAnimation.DESCRIPTOR,
this::createExternalInterface, this);
@@ -314,42 +287,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellController.addConfigurationChangeListener(this);
}
- private void setupAnimationDeveloperSettingsObserver(
- @NonNull ContentResolver contentResolver,
- @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
- if (predictiveBackSystemAnims()) {
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
- + "developer settings flag is ignored and no content observer registered");
- return;
- }
- ContentObserver settingsObserver = new ContentObserver(backgroundHandler) {
- @Override
- public void onChange(boolean selfChange, Uri uri) {
- updateEnableAnimationFromFlags();
- }
- };
- contentResolver.registerContentObserver(
- Global.getUriFor(Global.ENABLE_BACK_ANIMATION),
- false, settingsObserver, UserHandle.USER_SYSTEM
- );
- }
-
- /**
- * Updates {@link BackAnimationController#mEnableAnimations} based on the current values of the
- * aconfig flag and the developer settings flag
- */
- @ShellBackgroundThread
- private void updateEnableAnimationFromFlags() {
- boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
- mEnableAnimations.set(isEnabled);
- ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
- }
-
- private boolean isDeveloperSettingEnabled() {
- return Global.getInt(mContext.getContentResolver(),
- Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF) == SETTING_VALUE_ON;
- }
-
public BackAnimation getBackAnimationImpl() {
return mBackAnimation;
}
@@ -617,14 +554,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void startBackNavigation(@NonNull BackTouchTracker touchTracker) {
try {
startLatencyTracking();
- final BackAnimationAdapter adapter = mEnableAnimations.get()
- ? mBackAnimationAdapter : null;
- if (adapter != null && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) {
- adapter.updateSupportedAnimators(
+ if (mBackAnimationAdapter != null
+ && mShellBackAnimationRegistry.hasSupportedAnimatorsChanged()) {
+ mBackAnimationAdapter.updateSupportedAnimators(
mShellBackAnimationRegistry.getSupportedAnimators());
}
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
- mNavigationObserver, adapter);
+ mNavigationObserver, mBackAnimationAdapter);
onBackNavigationInfoReceived(mBackNavigationInfo, touchTracker);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
@@ -696,9 +632,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private boolean shouldDispatchToAnimator() {
- return mEnableAnimations.get()
- && mBackNavigationInfo != null
- && mBackNavigationInfo.isPrepareRemoteAnimation();
+ return mBackNavigationInfo != null && mBackNavigationInfo.isPrepareRemoteAnimation();
}
private void tryPilferPointers() {
@@ -1093,7 +1027,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
() -> mShellExecutor.execute(this::onBackAnimationFinished));
if (mApps.length >= 1) {
- mCurrentTracker.updateStartLocation();
BackMotionEvent startEvent = mCurrentTracker.createStartEvent(mApps[0]);
dispatchOnBackStarted(mActiveCallback, startEvent);
if (startEvent.getSwipeEdge() == EDGE_NONE) {
@@ -1194,7 +1127,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
private void dump(PrintWriter pw, String prefix) {
pw.println(prefix + "BackAnimationController state:");
- pw.println(prefix + " mEnableAnimations=" + mEnableAnimations.get());
pw.println(prefix + " mBackGestureStarted=" + mBackGestureStarted);
pw.println(prefix + " mPostCommitAnimationInProgress=" + mPostCommitAnimationInProgress);
pw.println(prefix + " mShouldStartOnNextMoveEvent=" + mShouldStartOnNextMoveEvent);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 1408b6efc7f9..2600bcc18f72 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -108,7 +108,6 @@ import com.android.wm.shell.recents.TaskStackTransitionObserver;
import com.android.wm.shell.shared.ShellTransitions;
import com.android.wm.shell.shared.TransactionPool;
import com.android.wm.shell.shared.annotations.ShellAnimationThread;
-import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -438,29 +437,24 @@ public abstract class WMShellBaseModule {
ShellInit shellInit,
ShellController shellController,
@ShellMainThread ShellExecutor shellExecutor,
- @ShellBackgroundThread Handler backgroundHandler,
BackAnimationBackground backAnimationBackground,
Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry,
ShellCommandHandler shellCommandHandler,
Transitions transitions,
@ShellMainThread Handler handler
) {
- if (BackAnimationController.IS_ENABLED) {
return shellBackAnimationRegistry.map(
(animations) ->
new BackAnimationController(
shellInit,
shellController,
shellExecutor,
- backgroundHandler,
context,
backAnimationBackground,
animations,
shellCommandHandler,
transitions,
handler));
- }
- return Optional.empty();
}
@BindsOptionalOf
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 03f388c9f1c9..9e2b9b20be16 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
@@ -117,6 +117,7 @@ public abstract class Pip2Module {
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
PhonePipMenuController pipMenuController,
+ PipUiEventLogger pipUiEventLogger,
@ShellMainThread ShellExecutor mainExecutor) {
if (!PipUtils.isPip2ExperimentEnabled()) {
return Optional.empty();
@@ -126,7 +127,7 @@ public abstract class Pip2Module {
displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
- mainExecutor));
+ pipUiEventLogger, mainExecutor));
}
}
@@ -188,11 +189,11 @@ public abstract class Pip2Module {
FloatingContentCoordinator floatingContentCoordinator,
PipScheduler pipScheduler,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
- PipBoundsAlgorithm pipBoundsAlgorithm,
- PipTransitionState pipTransitionState) {
+ PipTransitionState pipTransitionState,
+ PipUiEventLogger pipUiEventLogger) {
return new PipMotionHelper(context, pipBoundsState, menuController, pipSnapAlgorithm,
floatingContentCoordinator, pipScheduler, pipPerfHintControllerOptional,
- pipBoundsAlgorithm, pipTransitionState);
+ pipTransitionState, pipUiEventLogger);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index ee817b34b24a..050dfb6f562c 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
@@ -310,6 +310,11 @@ class DesktopTasksController(
transitions.startTransition(transitionType, wct, handler).also { t ->
handler?.setTransition(t)
}
+
+ // launch from recent DesktopTaskView
+ desktopModeEnterExitTransitionListener?.onEnterDesktopModeTransitionStarted(
+ FREEFORM_ANIMATION_DURATION
+ )
}
/** Gets number of visible freeform tasks in [displayId]. */
@@ -1344,7 +1349,7 @@ class DesktopTasksController(
private fun addWallpaperActivity(displayId: Int, wct: WindowContainerTransaction) {
logV("addWallpaperActivity")
- if (Flags.enableDesktopWallpaperActivityOnSystemUser()) {
+ if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
val intent = Intent(context, DesktopWallpaperActivity::class.java)
val options =
ActivityOptions.makeBasic().apply {
@@ -1393,7 +1398,7 @@ class DesktopTasksController(
private fun removeWallpaperActivity(wct: WindowContainerTransaction) {
desktopWallpaperActivityTokenProvider.getToken()?.let { token ->
logV("removeWallpaperActivity")
- if (Flags.enableDesktopWallpaperActivityOnSystemUser()) {
+ if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
wct.reorder(token, /* onTop= */ false)
} else {
wct.removeTask(token)
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 e7a00776360e..9bf5555fc194 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
@@ -236,7 +236,7 @@ class DesktopTasksTransitionObserver(
if (transitionToCloseWallpaper == transition) {
// TODO: b/362469671 - Handle merging the animation when desktop is also closing.
desktopWallpaperActivityTokenProvider.getToken()?.let { wallpaperActivityToken ->
- if (Flags.enableDesktopWallpaperActivityOnSystemUser()) {
+ if (Flags.enableDesktopWallpaperActivityForSystemUser()) {
transitions.startTransition(
TRANSIT_TO_BACK,
WindowContainerTransaction()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 491b577386d7..e24b2c5f0134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -332,7 +332,9 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData(),
event.getDragFlags());
- dragSession.initialize();
+ // Only update the running task for now to determine if we should defer to desktop to
+ // handle the drag
+ dragSession.updateRunningTask();
final ActivityManager.RunningTaskInfo taskInfo = dragSession.runningTaskInfo;
// Desktop tasks will have their own drag handling.
final boolean isDesktopDrag = taskInfo != null && taskInfo.isFreeform()
@@ -340,7 +342,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
pd.isHandlingDrag = DragUtils.canHandleDrag(event) && !isDesktopDrag;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
- pd.isHandlingDrag, event.getClipData().getItemCount(),
+ pd.isHandlingDrag,
+ event.getClipData() != null ? event.getClipData().getItemCount() : -1,
DragUtils.getMimeTypesConcatenated(description),
DragUtils.dragFlagsToString(event.getDragFlags()));
}
@@ -355,6 +358,8 @@ public class DragAndDropController implements RemoteCallable<DragAndDropControll
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
+ // Only initialize the session after we've checked that we're handling the drag
+ dragSession.initialize(true /* skipUpdateRunningTask */);
pd.dragSession = dragSession;
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index c4ff87d175a7..279452ee8b9b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -29,7 +29,6 @@ import android.content.ClipData;
import android.content.ClipDescription;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.os.PersistableBundle;
import androidx.annotation.Nullable;
@@ -44,6 +43,7 @@ import java.util.List;
*/
public class DragSession {
private final ActivityTaskManager mActivityTaskManager;
+ @Nullable
private final ClipData mInitialDragData;
private final int mInitialDragFlags;
@@ -66,7 +66,7 @@ public class DragSession {
@WindowConfiguration.ActivityType
int runningTaskActType = ACTIVITY_TYPE_STANDARD;
boolean dragItemSupportsSplitscreen;
- int hideDragSourceTaskId = -1;
+ final int hideDragSourceTaskId;
DragSession(ActivityTaskManager activityTaskManager,
DisplayLayout dispLayout, ClipData data, int dragFlags) {
@@ -83,7 +83,6 @@ public class DragSession {
/**
* Returns the clip description associated with the drag.
- * @return
*/
ClipDescription getClipDescription() {
return mInitialDragData.getDescription();
@@ -125,8 +124,10 @@ public class DragSession {
/**
* Updates the session data based on the current state of the system at the start of the drag.
*/
- void initialize() {
- updateRunningTask();
+ void initialize(boolean skipUpdateRunningTask) {
+ if (!skipUpdateRunningTask) {
+ updateRunningTask();
+ }
activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
// TODO: This should technically check & respect config_supportsNonResizableMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
index 248a1124cd86..a62dd1c83520 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragUtils.java
@@ -49,7 +49,7 @@ public class DragUtils {
* Returns whether we can handle this particular drag.
*/
public static boolean canHandleDrag(DragEvent event) {
- if (event.getClipData().getItemCount() <= 0) {
+ if (event.getClipData() == null || event.getClipData().getItemCount() <= 0) {
// No clip data, ignore this drag
return false;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index 8c6d5f5c6660..562b26014bf3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -59,6 +59,7 @@ 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.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -98,6 +99,7 @@ public class PipController implements ConfigurationChangeListener,
private final PipTouchHandler mPipTouchHandler;
private final PipAppOpsListener mPipAppOpsListener;
private final PhonePipMenuController mPipMenuController;
+ private final PipUiEventLogger mPipUiEventLogger;
private final ShellExecutor mMainExecutor;
private final PipImpl mImpl;
private final List<Consumer<Boolean>> mOnIsInPipStateChangedListeners = new ArrayList<>();
@@ -143,6 +145,7 @@ public class PipController implements ConfigurationChangeListener,
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
PhonePipMenuController pipMenuController,
+ PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
@@ -160,6 +163,7 @@ public class PipController implements ConfigurationChangeListener,
mPipTouchHandler = pipTouchHandler;
mPipAppOpsListener = pipAppOpsListener;
mPipMenuController = pipMenuController;
+ mPipUiEventLogger = pipUiEventLogger;
mMainExecutor = mainExecutor;
mImpl = new PipImpl();
@@ -187,6 +191,7 @@ public class PipController implements ConfigurationChangeListener,
PipTouchHandler pipTouchHandler,
PipAppOpsListener pipAppOpsListener,
PhonePipMenuController pipMenuController,
+ PipUiEventLogger pipUiEventLogger,
ShellExecutor mainExecutor) {
if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -197,7 +202,7 @@ public class PipController implements ConfigurationChangeListener,
displayController, displayInsetsController, pipBoundsState, pipBoundsAlgorithm,
pipDisplayLayoutState, pipScheduler, taskStackListener, shellTaskOrganizer,
pipTransitionState, pipTouchHandler, pipAppOpsListener, pipMenuController,
- mainExecutor);
+ pipUiEventLogger, mainExecutor);
}
public PipImpl getPipImpl() {
@@ -238,18 +243,6 @@ public class PipController implements ConfigurationChangeListener,
});
mPipAppOpsListener.setCallback(mPipTouchHandler.getMotionHelper());
- mPipTransitionState.addPipTransitionStateChangedListener(
- (oldState, newState, extra) -> {
- if (newState == PipTransitionState.ENTERED_PIP) {
- final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo();
- if (taskInfo != null && taskInfo.topActivity != null) {
- mPipAppOpsListener.onActivityPinned(
- taskInfo.topActivity.getPackageName());
- }
- } else if (newState == PipTransitionState.EXITED_PIP) {
- mPipAppOpsListener.onActivityUnpinned();
- }
- });
}
private ExternalInterfaceBinder createExternalInterface() {
@@ -446,14 +439,25 @@ public class PipController implements ConfigurationChangeListener,
mPipTransitionState.setSwipePipToHomeState(overlay, appBounds);
break;
case PipTransitionState.ENTERED_PIP:
+ final TaskInfo taskInfo = mPipTransitionState.getPipTaskInfo();
+ if (taskInfo != null && taskInfo.topActivity != null) {
+ mPipAppOpsListener.onActivityPinned(taskInfo.topActivity.getPackageName());
+ mPipUiEventLogger.setTaskInfo(taskInfo);
+ }
if (mPipTransitionState.isInSwipePipToHomeTransition()) {
+ mPipUiEventLogger.log(
+ PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_AUTO_ENTER);
mPipTransitionState.resetSwipePipToHomeState();
+ } else {
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_ENTER);
}
for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
listener.accept(true /* inPip */);
}
break;
case PipTransitionState.EXITED_PIP:
+ mPipAppOpsListener.onActivityUnpinned();
+ mPipUiEventLogger.setTaskInfo(null);
for (Consumer<Boolean> listener : mOnIsInPipStateChangedListeners) {
listener.accept(false /* inPip */);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 37296531ee34..9babe9e9e4eb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -43,20 +43,20 @@ import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
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.PipPerfHintController;
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
+import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
+import java.util.Optional;
+
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
-import java.util.Optional;
-
/**
* A helper to animate and manipulate the PiP.
*/
@@ -80,12 +80,12 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
private static final float DISMISS_CIRCLE_PERCENT = 0.85f;
private final Context mContext;
- private @NonNull PipBoundsState mPipBoundsState;
- private @NonNull PipBoundsAlgorithm mPipBoundsAlgorithm;
- private @NonNull PipScheduler mPipScheduler;
- private @NonNull PipTransitionState mPipTransitionState;
- private PhonePipMenuController mMenuController;
- private PipSnapAlgorithm mSnapAlgorithm;
+ @NonNull private final PipBoundsState mPipBoundsState;
+ @NonNull private final PipScheduler mPipScheduler;
+ @NonNull private final PipTransitionState mPipTransitionState;
+ @NonNull private final PipUiEventLogger mPipUiEventLogger;
+ private final PhonePipMenuController mMenuController;
+ private final PipSnapAlgorithm mSnapAlgorithm;
/** The region that all of PIP must stay within. */
private final Rect mFloatingAllowedArea = new Rect();
@@ -168,10 +168,9 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
PhonePipMenuController menuController, PipSnapAlgorithm snapAlgorithm,
FloatingContentCoordinator floatingContentCoordinator, PipScheduler pipScheduler,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
- PipBoundsAlgorithm pipBoundsAlgorithm, PipTransitionState pipTransitionState) {
+ PipTransitionState pipTransitionState, PipUiEventLogger pipUiEventLogger) {
mContext = context;
mPipBoundsState = pipBoundsState;
- mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipScheduler = pipScheduler;
mMenuController = menuController;
mSnapAlgorithm = snapAlgorithm;
@@ -185,6 +184,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
};
mPipTransitionState = pipTransitionState;
mPipTransitionState.addPipTransitionStateChangedListener(this);
+ mPipUiEventLogger = pipUiEventLogger;
}
void init() {
@@ -850,9 +850,11 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
if (mPipBoundsState.getBounds().left < 0
&& mPipBoundsState.getStashedState() != STASH_TYPE_LEFT) {
mPipBoundsState.setStashed(STASH_TYPE_LEFT);
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_LEFT);
} else if (mPipBoundsState.getBounds().left >= 0
&& mPipBoundsState.getStashedState() != STASH_TYPE_RIGHT) {
mPipBoundsState.setStashed(STASH_TYPE_RIGHT);
+ mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_STASH_RIGHT);
}
mMenuController.hideMenu();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl
index 964e5fd62a5f..af1679f2d175 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentsAnimationController.aidl
@@ -36,12 +36,6 @@ import com.android.internal.os.IResultReceiver;
interface IRecentsAnimationController {
/**
- * Takes a screenshot of the task associated with the given {@param taskId}. Only valid for the
- * current set of task ids provided to the handler.
- */
- TaskSnapshot screenshotTask(int taskId);
-
- /**
* Sets the final surface transaction on a Task. This is used by Launcher to notify the system
* that animating Activity to PiP has completed and the associated task surface should be
* updated accordingly. This should be called before `finish`
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 032dac9ff3a2..76496b06a4dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -1227,19 +1227,6 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
}
@Override
- public TaskSnapshot screenshotTask(int taskId) {
- try {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
- "[%d] RecentsController.screenshotTask: taskId=%d", mInstanceId, taskId);
- return ActivityTaskManager.getService().takeTaskSnapshot(taskId,
- true /* updateCache */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to screenshot task", e);
- }
- return null;
- }
-
- @Override
public void setInputConsumerEnabled(boolean enabled) {
mExecutor.execute(() -> {
if (mFinishCB == null || !enabled) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 90c59176b991..5aa329108596 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -2166,7 +2166,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.setForceTranslucent(mRootTaskInfo.token, translucent);
}
- /** Callback when split roots visiblility changed. */
+ /** Callback when split roots visiblility changed.
+ * NOTICE: This only be called on legacy transition. */
@Override
public void onStageVisibilityChanged(StageTaskListener stageListener) {
// If split didn't active, just ignore this callback because we should already did these
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index bfe74122c5c2..021f6595d984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -240,20 +240,12 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN,
- "onTaskInfoChanged: taskId=%d vis=%b reqVis=%b baseAct=%s stageId=%s",
- taskInfo.taskId, taskInfo.isVisible, taskInfo.isVisibleRequested,
- taskInfo.baseActivity, stageTypeToString(mId));
+ ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: taskId=%d taskAct=%s "
+ + "stageId=%s",
+ taskInfo.taskId, taskInfo.baseActivity, stageTypeToString(mId));
mWindowDecorViewModel.ifPresent(viewModel -> viewModel.onTaskInfoChanged(taskInfo));
if (mRootTaskInfo.taskId == taskInfo.taskId) {
mRootTaskInfo = taskInfo;
- boolean isVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
- if (mVisible != isVisible) {
- ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTaskInfoChanged: currentVis=%b newVis=%b",
- mVisible, isVisible);
- mVisible = isVisible;
- mCallbacks.onStageVisibilityChanged(this);
- }
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
if (!taskInfo.supportsMultiWindow
|| !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
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 3f65d9318692..1264c013faf5 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
@@ -231,6 +231,7 @@ internal class AppHandleViewHolder(
fun disposeStatusBarInputLayer() {
if (!statusBarInputLayerExists) return
statusBarInputLayerExists = false
+ statusBarInputLayer?.view?.setOnTouchListener(null)
handler.post {
statusBarInputLayer?.releaseView()
statusBarInputLayer = null
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 47ee7bb20199..bbdb90f0a37c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -61,9 +61,7 @@ import android.os.RemoteCallback;
import android.os.RemoteException;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableContentResolver;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
import android.view.KeyEvent;
@@ -84,7 +82,6 @@ import android.window.WindowContainerToken;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
-import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -109,7 +106,6 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class BackAnimationControllerTest extends ShellTestCase {
- private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
private ShellInit mShellInit;
@@ -148,8 +144,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
private Transitions.TransitionHandler mTakeoverHandler;
private BackAnimationController mController;
- private TestableContentResolver mContentResolver;
- private TestableLooper mTestableLooper;
private DefaultCrossActivityBackAnimation mDefaultCrossActivityBackAnimation;
private CrossTaskBackAnimation mCrossTaskBackAnimation;
@@ -166,11 +160,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mContext.addMockSystemService(InputManager.class, mInputManager);
mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
- mContentResolver = new TestableContentResolver(mContext);
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
- ANIMATION_ENABLED);
- mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mDefaultCrossActivityBackAnimation = new DefaultCrossActivityBackAnimation(mContext,
mAnimationBackground, mRootTaskDisplayAreaOrganizer, mHandler);
@@ -187,10 +176,8 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellInit,
mShellController,
mShellExecutor,
- new Handler(mTestableLooper.getLooper()),
mActivityTaskManager,
mContext,
- mContentResolver,
mAnimationBackground,
mShellBackAnimationRegistry,
mShellCommandHandler,
@@ -342,47 +329,6 @@ public class BackAnimationControllerTest extends ShellTestCase {
}
@Test
- public void animationDisabledFromSettings() throws RemoteException {
- // Toggle the setting off
- Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
- ShellInit shellInit = new ShellInit(mShellExecutor);
- mController =
- new BackAnimationController(
- shellInit,
- mShellController,
- mShellExecutor,
- new Handler(mTestableLooper.getLooper()),
- mActivityTaskManager,
- mContext,
- mContentResolver,
- mAnimationBackground,
- mShellBackAnimationRegistry,
- mShellCommandHandler,
- mTransitions,
- mHandler);
- shellInit.init();
- registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
-
- ArgumentCaptor<BackMotionEvent> backEventCaptor =
- ArgumentCaptor.forClass(BackMotionEvent.class);
-
- createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
- /* enableAnimation = */ false,
- /* isAnimationCallback = */ false);
-
- triggerBackGesture();
- releaseBackGesture();
-
- verify(mAppCallback, times(1)).onBackInvoked();
-
- verify(mAnimatorCallback, never()).onBackStarted(any());
- verify(mAnimatorCallback, never()).onBackProgressed(backEventCaptor.capture());
- verify(mAnimatorCallback, never()).onBackInvoked();
- verify(mBackAnimationRunner, never()).onAnimationStart(
- anyInt(), any(), any(), any(), any());
- }
-
- @Test
public void gestureQueued_WhenPreviousTransitionHasNotYetEnded() throws RemoteException {
registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME,
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 da27c08920dc..692b50303038 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
@@ -1497,7 +1497,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveToFullscreen_tdaFullscreen_windowingModeUndefined_removesWallpaperActivity() {
val task = setUpFreeformTask()
assertNotNull(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY))
@@ -1530,7 +1530,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveToFullscreen_tdaFreeform_windowingModeFullscreen_removesWallpaperActivity() {
val task = setUpFreeformTask()
@@ -1965,7 +1965,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun onDesktopWindowClose_singleActiveTask_hasWallpaperActivityToken() {
val task = setUpFreeformTask()
@@ -2011,7 +2011,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun onDesktopWindowClose_multipleActiveTasks_isOnlyNonClosingTask() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -2025,7 +2025,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun onDesktopWindowClose_multipleActiveTasks_hasMinimized() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -2095,7 +2095,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun onTaskMinimize_singleActiveTask_hasWallpaperActivityToken_removesWallpaper() {
val task = setUpFreeformTask()
val transition = Binder()
@@ -2147,7 +2147,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun onDesktopWindowMinimize_multipleActiveTasks_minimizesTheOnlyVisibleTask_removesWallpaper() {
val task1 = setUpFreeformTask(active = true)
val task2 = setUpFreeformTask(active = true)
@@ -2808,7 +2808,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_backTransition_singleTaskWithToken_removesWallpaper() {
val task = setUpFreeformTask()
@@ -2849,7 +2849,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_backTransition_multipleTasksSingleNonClosing_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2867,7 +2867,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_backTransition_multipleTasksSingleNonMinimized_removesWallpaperAndTask() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2934,7 +2934,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_closeTransition_singleTaskWithToken_withWallpaper_removesWallpaper() {
val task = setUpFreeformTask()
@@ -2974,7 +2974,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_closeTransition_multipleTasksSingleNonClosing_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -2992,7 +2992,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(
Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
- Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER,
+ Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER,
)
fun handleRequest_closeTransition_multipleTasksSingleNonMinimized_removesWallpaper() {
val task1 = setUpFreeformTask(displayId = DEFAULT_DISPLAY)
@@ -3084,7 +3084,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun moveFocusedTaskToFullscreen_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
@@ -3596,7 +3596,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun enterSplit_onlyVisibleNonMinimizedTask_removesWallpaperActivity() {
val task1 = setUpFreeformTask()
val task2 = setUpFreeformTask()
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 3cc30cb491b3..89ab65a42bbf 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
@@ -239,7 +239,7 @@ class DesktopTasksTransitionObserverTest {
}
@Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_ON_SYSTEM_USER)
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WALLPAPER_ACTIVITY_FOR_SYSTEM_USER)
fun closeLastTask_wallpaperTokenExists_wallpaperIsRemoved() {
val mockTransition = Mockito.mock(IBinder::class.java)
val task = createTaskInfo(1, WINDOWING_MODE_FREEFORM)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index e40bbad7adda..32bb8bbdbbe3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -150,4 +150,25 @@ public class DragAndDropControllerTest extends ShellTestCase {
mController.onDrag(dragLayout, event);
verify(mDragAndDropListener, never()).onDragStarted();
}
+
+ @Test
+ public void testOnDragStarted_withNoClipData() {
+ final View dragLayout = mock(View.class);
+ final Display display = mock(Display.class);
+ doReturn(display).when(dragLayout).getDisplay();
+ doReturn(DEFAULT_DISPLAY).when(display).getDisplayId();
+
+ final ClipData clipData = createAppClipData(MIMETYPE_APPLICATION_SHORTCUT);
+ final DragEvent event = mock(DragEvent.class);
+ doReturn(ACTION_DRAG_STARTED).when(event).getAction();
+ doReturn(null).when(event).getClipData();
+ doReturn(clipData.getDescription()).when(event).getClipDescription();
+
+ // Ensure there's a target so that onDrag will execute
+ mController.addDisplayDropTarget(0, mContext, mock(WindowManager.class),
+ mock(FrameLayout.class), mock(DragLayout.class));
+
+ // Verify the listener is called on a valid drag action.
+ mController.onDrag(dragLayout, event);
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
index 0cf15baf30b0..a284663d9a38 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/SplitDragPolicyTest.java
@@ -220,7 +220,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.initialize();
+ dragSession.initialize(false /* skipUpdateRunningTask */);
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
@@ -235,7 +235,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.initialize();
+ dragSession.initialize(false /* skipUpdateRunningTask */);
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -255,7 +255,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mPortraitDisplayLayout, data, 0 /* dragFlags */);
- dragSession.initialize();
+ dragSession.initialize(false /* skipUpdateRunningTask */);
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -276,7 +276,7 @@ public class SplitDragPolicyTest extends ShellTestCase {
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */);
- dragSession.initialize();
+ dragSession.initialize(false /* skipUpdateRunningTask */);
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 7dac0859b7e9..6b02aeffd42a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -20,7 +20,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.app.assist.AssistContent.EXTRA_SESSION_TRANSFER_WEB_URI;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING;
import static android.view.InsetsSource.FLAG_FORCE_CONSUMING_OPAQUE_CAPTION_BAR;
@@ -1176,7 +1175,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void webUriLink_webUriLinkUsedWhenWhenAvailable() {
+ public void sessionTransferUri_sessionTransferUriUsedWhenWhenAvailable() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
@@ -1188,7 +1187,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void webUriLink_webUriLinkUsedWhenSessionTransferUriUnavailable() {
+ public void webUri_webUriUsedWhenSessionTransferUriUnavailable() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, TEST_URI1 /* captured link */, TEST_URI2 /* web uri */,
@@ -1200,7 +1199,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_APP_TO_WEB)
- public void genericLink_genericLinkUsedWhenCapturedLinkAndWebUriUnavailable() {
+ public void genericLink_genericLinkUsedWhenCapturedLinkAndAssistContentUriUnavailable() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
final DesktopModeWindowDecoration decor = createWindowDecoration(
taskInfo, null /* captured link */, null /* web uri */,
@@ -1490,7 +1489,7 @@ public class DesktopModeWindowDecorationTests extends ShellTestCase {
taskInfo.capturedLink = capturedLink;
taskInfo.capturedLinkTimestamp = System.currentTimeMillis();
mAssistContent.setWebUri(webUri);
- mAssistContent.getExtras().putObject(EXTRA_SESSION_TRANSFER_WEB_URI, sessionTransferUri);
+ mAssistContent.setSessionTransferUri(sessionTransferUri);
final String genericLinkString = genericLink == null ? null : genericLink.toString();
doReturn(genericLinkString).when(mMockGenericLinksParser).getGenericLink(any());
// Relayout to set captured link
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index dbb891455ddd..e693fcfd3918 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -162,10 +162,13 @@ const std::string& ApkAssets::GetDebugName() const {
return assets_provider_->GetDebugName();
}
-bool ApkAssets::IsUpToDate() const {
+UpToDate ApkAssets::IsUpToDate() const {
// Loaders are invalidated by the app, not the system, so assume they are up to date.
- return IsLoader() || ((!loaded_idmap_ || loaded_idmap_->IsUpToDate())
- && assets_provider_->IsUpToDate());
+ if (IsLoader()) {
+ return UpToDate::Always;
+ }
+ const auto idmap_res = loaded_idmap_ ? loaded_idmap_->IsUpToDate() : UpToDate::Always;
+ return combine(idmap_res, [this] { return assets_provider_->IsUpToDate(); });
}
} // namespace android
diff --git a/libs/androidfw/AssetsProvider.cpp b/libs/androidfw/AssetsProvider.cpp
index 2d3c06506a1f..11b12eb030a6 100644
--- a/libs/androidfw/AssetsProvider.cpp
+++ b/libs/androidfw/AssetsProvider.cpp
@@ -24,9 +24,8 @@
#include <ziparchive/zip_archive.h>
namespace android {
-namespace {
-constexpr const char* kEmptyDebugString = "<empty>";
-} // namespace
+
+static constexpr std::string_view kEmptyDebugString = "<empty>";
std::unique_ptr<Asset> AssetsProvider::Open(const std::string& path, Asset::AccessMode mode,
bool* file_exists) const {
@@ -86,11 +85,9 @@ void ZipAssetsProvider::ZipCloser::operator()(ZipArchive* a) const {
}
ZipAssetsProvider::ZipAssetsProvider(ZipArchiveHandle handle, PathOrDebugName&& path,
- package_property_t flags, time_t last_mod_time)
- : zip_handle_(handle),
- name_(std::move(path)),
- flags_(flags),
- last_mod_time_(last_mod_time) {}
+ package_property_t flags, ModDate last_mod_time)
+ : zip_handle_(handle), name_(std::move(path)), flags_(flags), last_mod_time_(last_mod_time) {
+}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
package_property_t flags,
@@ -104,10 +101,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
return {};
}
- struct stat sb{.st_mtime = -1};
+ ModDate mod_date = kInvalidModDate;
// Skip all up-to-date checks if the file won't ever change.
- if (!isReadonlyFilesystem(path.c_str())) {
- if ((released_fd < 0 ? stat(path.c_str(), &sb) : fstat(released_fd, &sb)) < 0) {
+ if (isKnownWritablePath(path.c_str()) || !isReadonlyFilesystem(GetFileDescriptor(handle))) {
+ if (mod_date = getFileModDate(GetFileDescriptor(handle)); mod_date == kInvalidModDate) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -116,7 +113,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(std::string path,
}
return std::unique_ptr<ZipAssetsProvider>(
- new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, sb.st_mtime));
+ new ZipAssetsProvider(handle, PathOrDebugName::Path(std::move(path)), flags, mod_date));
}
std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
@@ -137,10 +134,10 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
return {};
}
- struct stat sb{.st_mtime = -1};
+ ModDate mod_date = kInvalidModDate;
// Skip all up-to-date checks if the file won't ever change.
if (!isReadonlyFilesystem(released_fd)) {
- if (fstat(released_fd, &sb) < 0) {
+ if (mod_date = getFileModDate(released_fd); mod_date == kInvalidModDate) {
// Stat requires execute permissions on all directories path to the file. If the process does
// not have execute permissions on this file, allow the zip to be opened but IsUpToDate() will
// always have to return true.
@@ -150,7 +147,7 @@ std::unique_ptr<ZipAssetsProvider> ZipAssetsProvider::Create(base::unique_fd fd,
}
return std::unique_ptr<ZipAssetsProvider>(new ZipAssetsProvider(
- handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, sb.st_mtime));
+ handle, PathOrDebugName::DebugName(std::move(friendly_name)), flags, mod_date));
}
std::unique_ptr<Asset> ZipAssetsProvider::OpenInternal(const std::string& path,
@@ -282,21 +279,16 @@ const std::string& ZipAssetsProvider::GetDebugName() const {
return name_.GetDebugName();
}
-bool ZipAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == -1) {
- return true;
- }
- struct stat sb{};
- if (fstat(GetFileDescriptor(zip_handle_.get()), &sb) < 0) {
- // If fstat fails on the zip archive, return true so the zip archive the resource system does
- // attempt to refresh the ApkAsset.
- return true;
+UpToDate ZipAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
}
- return last_mod_time_ == sb.st_mtime;
+ return fromBool(last_mod_time_ == getFileModDate(GetFileDescriptor(zip_handle_.get())));
}
-DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, time_t last_mod_time)
- : dir_(std::move(path)), last_mod_time_(last_mod_time) {}
+DirectoryAssetsProvider::DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time)
+ : dir_(std::move(path)), last_mod_time_(last_mod_time) {
+}
std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::string path) {
struct stat sb;
@@ -317,7 +309,7 @@ std::unique_ptr<DirectoryAssetsProvider> DirectoryAssetsProvider::Create(std::st
const bool isReadonly = isReadonlyFilesystem(path.c_str());
return std::unique_ptr<DirectoryAssetsProvider>(
- new DirectoryAssetsProvider(std::move(path), isReadonly ? -1 : sb.st_mtime));
+ new DirectoryAssetsProvider(std::move(path), isReadonly ? kInvalidModDate : getModDate(sb)));
}
std::unique_ptr<Asset> DirectoryAssetsProvider::OpenInternal(const std::string& path,
@@ -346,17 +338,11 @@ const std::string& DirectoryAssetsProvider::GetDebugName() const {
return dir_;
}
-bool DirectoryAssetsProvider::IsUpToDate() const {
- if (last_mod_time_ == -1) {
- return true;
- }
- struct stat sb;
- if (stat(dir_.c_str(), &sb) < 0) {
- // If stat fails on the zip archive, return true so the zip archive the resource system does
- // attempt to refresh the ApkAsset.
- return true;
+UpToDate DirectoryAssetsProvider::IsUpToDate() const {
+ if (last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
}
- return last_mod_time_ == sb.st_mtime;
+ return fromBool(last_mod_time_ == getFileModDate(dir_.c_str()));
}
MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& primary,
@@ -369,8 +355,14 @@ MultiAssetsProvider::MultiAssetsProvider(std::unique_ptr<AssetsProvider>&& prima
std::unique_ptr<AssetsProvider> MultiAssetsProvider::Create(
std::unique_ptr<AssetsProvider>&& primary, std::unique_ptr<AssetsProvider>&& secondary) {
- if (primary == nullptr || secondary == nullptr) {
- return nullptr;
+ if (primary == nullptr && secondary == nullptr) {
+ return EmptyAssetsProvider::Create();
+ }
+ if (!primary) {
+ return secondary;
+ }
+ if (!secondary) {
+ return primary;
}
return std::unique_ptr<MultiAssetsProvider>(new MultiAssetsProvider(std::move(primary),
std::move(secondary)));
@@ -397,8 +389,8 @@ const std::string& MultiAssetsProvider::GetDebugName() const {
return debug_name_;
}
-bool MultiAssetsProvider::IsUpToDate() const {
- return primary_->IsUpToDate() && secondary_->IsUpToDate();
+UpToDate MultiAssetsProvider::IsUpToDate() const {
+ return combine(primary_->IsUpToDate(), [this] { return secondary_->IsUpToDate(); });
}
EmptyAssetsProvider::EmptyAssetsProvider(std::optional<std::string>&& path) :
@@ -438,12 +430,12 @@ const std::string& EmptyAssetsProvider::GetDebugName() const {
if (path_.has_value()) {
return *path_;
}
- const static std::string kEmpty = kEmptyDebugString;
+ constexpr static std::string kEmpty{kEmptyDebugString};
return kEmpty;
}
-bool EmptyAssetsProvider::IsUpToDate() const {
- return true;
+UpToDate EmptyAssetsProvider::IsUpToDate() const {
+ return UpToDate::Always;
}
} // namespace android
diff --git a/libs/androidfw/Idmap.cpp b/libs/androidfw/Idmap.cpp
index 3ecd82b074a1..262e7df185b7 100644
--- a/libs/androidfw/Idmap.cpp
+++ b/libs/androidfw/Idmap.cpp
@@ -22,9 +22,10 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
-#include "androidfw/misc.h"
+#include "androidfw/AssetManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
+#include "androidfw/misc.h"
#include "utils/ByteOrder.h"
#include "utils/Trace.h"
@@ -268,11 +269,16 @@ LoadedIdmap::LoadedIdmap(const std::string& idmap_path, const Idmap_header* head
configurations_(configs),
overlay_entries_(overlay_entries),
string_pool_(std::move(string_pool)),
- idmap_fd_(
- android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH)),
overlay_apk_path_(overlay_apk_path),
target_apk_path_(target_apk_path),
- idmap_last_mod_time_(getFileModDate(idmap_fd_.get())) {
+ idmap_last_mod_time_(kInvalidModDate) {
+ if (!isReadonlyFilesystem(std::string(overlay_apk_path_).c_str()) ||
+ !(target_apk_path_ == AssetManager::TARGET_APK_PATH ||
+ isReadonlyFilesystem(std::string(target_apk_path_).c_str()))) {
+ idmap_fd_.reset(
+ android::base::utf8::open(idmap_path.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY | O_PATH));
+ idmap_last_mod_time_ = getFileModDate(idmap_fd_);
+ }
}
std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPiece idmap_data) {
@@ -381,8 +387,11 @@ std::unique_ptr<LoadedIdmap> LoadedIdmap::Load(StringPiece idmap_path, StringPie
overlay_entries, std::move(idmap_string_pool), *overlay_path, *target_path));
}
-bool LoadedIdmap::IsUpToDate() const {
- return idmap_last_mod_time_ == getFileModDate(idmap_fd_.get());
+UpToDate LoadedIdmap::IsUpToDate() const {
+ if (idmap_last_mod_time_ == kInvalidModDate) {
+ return UpToDate::Always;
+ }
+ return fromBool(idmap_last_mod_time_ == getFileModDate(idmap_fd_.get()));
}
} // namespace android
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index de9991a8be5e..a8eb062a2ece 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -152,12 +152,11 @@ static void fill9patchOffsets(Res_png_9patch* patch) {
patch->colorsOffset = patch->yDivsOffset + (patch->numYDivs * sizeof(int32_t));
}
-void Res_value::copyFrom_dtoh(const Res_value& src)
-{
- size = dtohs(src.size);
- res0 = src.res0;
- dataType = src.dataType;
- data = dtohl(src.data);
+void Res_value::copyFrom_dtoh_slow(const Res_value& src) {
+ size = dtohs(src.size);
+ res0 = src.res0;
+ dataType = src.dataType;
+ data = dtohl(src.data);
}
void Res_png_9patch::deviceToFile()
@@ -2031,16 +2030,6 @@ status_t ResXMLTree::validateNode(const ResXMLTree_node* node) const
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
- const size_t size = dtohl(o.size);
- if (size >= sizeof(ResTable_config)) {
- *this = o;
- } else {
- memcpy(this, &o, size);
- memset(((uint8_t*)this)+size, 0, sizeof(ResTable_config)-size);
- }
-}
-
/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
char out[4]) {
if (in[0] & 0x80) {
@@ -2105,34 +2094,33 @@ size_t ResTable_config::unpackRegion(char region[4]) const {
return unpackLanguageOrRegion(this->country, '0', region);
}
-
-void ResTable_config::copyFromDtoH(const ResTable_config& o) {
- copyFromDeviceNoSwap(o);
- size = sizeof(ResTable_config);
- mcc = dtohs(mcc);
- mnc = dtohs(mnc);
- density = dtohs(density);
- screenWidth = dtohs(screenWidth);
- screenHeight = dtohs(screenHeight);
- sdkVersion = dtohs(sdkVersion);
- minorVersion = dtohs(minorVersion);
- smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
- screenWidthDp = dtohs(screenWidthDp);
- screenHeightDp = dtohs(screenHeightDp);
-}
-
-void ResTable_config::swapHtoD() {
- size = htodl(size);
- mcc = htods(mcc);
- mnc = htods(mnc);
- density = htods(density);
- screenWidth = htods(screenWidth);
- screenHeight = htods(screenHeight);
- sdkVersion = htods(sdkVersion);
- minorVersion = htods(minorVersion);
- smallestScreenWidthDp = htods(smallestScreenWidthDp);
- screenWidthDp = htods(screenWidthDp);
- screenHeightDp = htods(screenHeightDp);
+void ResTable_config::copyFromDtoH_slow(const ResTable_config& o) {
+ copyFromDeviceNoSwap(o);
+ size = sizeof(ResTable_config);
+ mcc = dtohs(mcc);
+ mnc = dtohs(mnc);
+ density = dtohs(density);
+ screenWidth = dtohs(screenWidth);
+ screenHeight = dtohs(screenHeight);
+ sdkVersion = dtohs(sdkVersion);
+ minorVersion = dtohs(minorVersion);
+ smallestScreenWidthDp = dtohs(smallestScreenWidthDp);
+ screenWidthDp = dtohs(screenWidthDp);
+ screenHeightDp = dtohs(screenHeightDp);
+}
+
+void ResTable_config::swapHtoD_slow() {
+ size = htodl(size);
+ mcc = htods(mcc);
+ mnc = htods(mnc);
+ density = htods(density);
+ screenWidth = htods(screenWidth);
+ screenHeight = htods(screenHeight);
+ sdkVersion = htods(sdkVersion);
+ minorVersion = htods(minorVersion);
+ smallestScreenWidthDp = htods(smallestScreenWidthDp);
+ screenWidthDp = htods(screenWidthDp);
+ screenHeightDp = htods(screenHeightDp);
}
/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
@@ -2145,7 +2133,7 @@ void ResTable_config::swapHtoD() {
// systems should happen very infrequently (if at all.)
// The comparison code relies on memcmp low-level optimizations that make it
// more efficient than strncmp.
- const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
+ static constexpr char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index be55fe8b4bb6..86c459fb4647 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -32,13 +32,18 @@ namespace android {
namespace util {
void ReadUtf16StringFromDevice(const uint16_t* src, size_t len, std::string* out) {
- char buf[5];
- while (*src && len != 0) {
- char16_t c = static_cast<char16_t>(dtohs(*src));
- utf16_to_utf8(&c, 1, buf, sizeof(buf));
- out->append(buf, strlen(buf));
- ++src;
- --len;
+ static constexpr bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+ if constexpr (kDeviceEndiannessSame) {
+ *out = Utf16ToUtf8({(const char16_t*)src, strnlen16((const char16_t*)src, len)});
+ } else {
+ char buf[5];
+ while (*src && len != 0) {
+ char16_t c = static_cast<char16_t>(dtohs(*src));
+ utf16_to_utf8(&c, 1, buf, sizeof(buf));
+ out->append(buf, strlen(buf));
+ ++src;
+ --len;
+ }
}
}
@@ -63,8 +68,10 @@ std::string Utf16ToUtf8(StringPiece16 utf16) {
}
std::string utf8;
- utf8.resize(utf8_length);
- utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin(), utf8_length + 1);
+ utf8.resize_and_overwrite(utf8_length, [&utf16](char* data, size_t size) {
+ utf16_to_utf8(utf16.data(), utf16.length(), data, size + 1);
+ return size;
+ });
return utf8;
}
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 231808beb718..3f6f4661f2f7 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -116,7 +116,7 @@ class ApkAssets : public RefBase {
return resources_asset_ != nullptr && resources_asset_->isAllocated();
}
- bool IsUpToDate() const;
+ UpToDate IsUpToDate() const;
// DANGER!
// This is a destructive method that rips the assets provider out of ApkAssets object.
diff --git a/libs/androidfw/include/androidfw/AssetsProvider.h b/libs/androidfw/include/androidfw/AssetsProvider.h
index d33c325ff369..e3b3ae41f7f4 100644
--- a/libs/androidfw/include/androidfw/AssetsProvider.h
+++ b/libs/androidfw/include/androidfw/AssetsProvider.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef ANDROIDFW_ASSETSPROVIDER_H
-#define ANDROIDFW_ASSETSPROVIDER_H
+#pragma once
#include <memory>
#include <string>
@@ -58,7 +57,7 @@ struct AssetsProvider {
WARN_UNUSED virtual const std::string& GetDebugName() const = 0;
// Returns whether the interface provides the most recent version of its files.
- WARN_UNUSED virtual bool IsUpToDate() const = 0;
+ WARN_UNUSED virtual UpToDate IsUpToDate() const = 0;
// Creates an Asset from a file on disk.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
@@ -95,7 +94,7 @@ struct ZipAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
WARN_UNUSED std::optional<uint32_t> GetCrc(std::string_view path) const;
~ZipAssetsProvider() override = default;
@@ -106,7 +105,7 @@ struct ZipAssetsProvider : public AssetsProvider {
private:
struct PathOrDebugName;
ZipAssetsProvider(ZipArchive* handle, PathOrDebugName&& path, package_property_t flags,
- time_t last_mod_time);
+ ModDate last_mod_time);
struct PathOrDebugName {
static PathOrDebugName Path(std::string value) {
@@ -135,7 +134,7 @@ struct ZipAssetsProvider : public AssetsProvider {
std::unique_ptr<ZipArchive, ZipCloser> zip_handle_;
PathOrDebugName name_;
package_property_t flags_;
- time_t last_mod_time_;
+ ModDate last_mod_time_;
};
// Supplies assets from a root directory.
@@ -147,7 +146,7 @@ struct DirectoryAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~DirectoryAssetsProvider() override = default;
protected:
@@ -156,23 +155,23 @@ struct DirectoryAssetsProvider : public AssetsProvider {
bool* file_exists) const override;
private:
- explicit DirectoryAssetsProvider(std::string&& path, time_t last_mod_time);
+ explicit DirectoryAssetsProvider(std::string&& path, ModDate last_mod_time);
std::string dir_;
- time_t last_mod_time_;
+ ModDate last_mod_time_;
};
// Supplies assets from a `primary` asset provider and falls back to supplying assets from the
// `secondary` asset provider if the asset cannot be found in the `primary`.
struct MultiAssetsProvider : public AssetsProvider {
static std::unique_ptr<AssetsProvider> Create(std::unique_ptr<AssetsProvider>&& primary,
- std::unique_ptr<AssetsProvider>&& secondary);
+ std::unique_ptr<AssetsProvider>&& secondary = {});
bool ForEachFile(const std::string& root_path,
base::function_ref<void(StringPiece, FileType)> f) const override;
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~MultiAssetsProvider() override = default;
protected:
@@ -199,7 +198,7 @@ struct EmptyAssetsProvider : public AssetsProvider {
WARN_UNUSED std::optional<std::string_view> GetPath() const override;
WARN_UNUSED const std::string& GetDebugName() const override;
- WARN_UNUSED bool IsUpToDate() const override;
+ WARN_UNUSED UpToDate IsUpToDate() const override;
~EmptyAssetsProvider() override = default;
protected:
@@ -212,5 +211,3 @@ struct EmptyAssetsProvider : public AssetsProvider {
};
} // namespace android
-
-#endif /* ANDROIDFW_ASSETSPROVIDER_H */
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index ac75eb3bb98c..87f3c9df9a91 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef IDMAP_H_
-#define IDMAP_H_
+#pragma once
#include <memory>
#include <string>
@@ -32,6 +31,31 @@
namespace android {
+// An enum that tracks more states than just 'up to date' or 'not' for a resources container:
+// there are several cases where we know for sure that the object can't change and won't get
+// out of date. Reporting those states to the managed layer allows it to stop checking here
+// completely, speeding up the cache lookups by dozens of milliseconds.
+enum class UpToDate : int { False, True, Always };
+
+// Combines two UpToDate values, and only accesses the second one if it matters to the result.
+template <class Getter>
+UpToDate combine(UpToDate first, Getter secondGetter) {
+ switch (first) {
+ case UpToDate::False:
+ return UpToDate::False;
+ case UpToDate::True: {
+ const auto second = secondGetter();
+ return second == UpToDate::False ? UpToDate::False : UpToDate::True;
+ }
+ case UpToDate::Always:
+ return secondGetter();
+ }
+}
+
+inline UpToDate fromBool(bool value) {
+ return value ? UpToDate::True : UpToDate::False;
+}
+
class LoadedIdmap;
class IdmapResMap;
struct Idmap_header;
@@ -196,7 +220,7 @@ class LoadedIdmap {
// Returns whether the idmap file on disk has not been modified since the construction of this
// LoadedIdmap.
- bool IsUpToDate() const;
+ UpToDate IsUpToDate() const;
protected:
// Exposed as protected so that tests can subclass and mock this class out.
@@ -231,5 +255,3 @@ class LoadedIdmap {
};
} // namespace android
-
-#endif // IDMAP_H_
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index e330410ed1a0..819fe4b38c87 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -47,6 +47,8 @@
namespace android {
+constexpr const bool kDeviceEndiannessSame = dtohs(0x1001) == 0x1001;
+
constexpr const uint32_t kIdmapMagic = 0x504D4449u;
constexpr const uint32_t kIdmapCurrentVersion = 0x0000000Au;
@@ -408,7 +410,16 @@ struct Res_value
typedef uint32_t data_type;
data_type data;
- void copyFrom_dtoh(const Res_value& src);
+ void copyFrom_dtoh(const Res_value& src) {
+ if constexpr (kDeviceEndiannessSame) {
+ *this = src;
+ } else {
+ copyFrom_dtoh_slow(src);
+ }
+ }
+
+ private:
+ void copyFrom_dtoh_slow(const Res_value& src);
};
/**
@@ -1254,11 +1265,32 @@ struct ResTable_config
// Varies in length from 3 to 8 chars. Zero-filled value.
char localeNumberingSystem[8];
- void copyFromDeviceNoSwap(const ResTable_config& o);
-
- void copyFromDtoH(const ResTable_config& o);
-
- void swapHtoD();
+ void copyFromDeviceNoSwap(const ResTable_config& o) {
+ const auto o_size = dtohl(o.size);
+ if (o_size >= sizeof(ResTable_config)) [[likely]] {
+ *this = o;
+ } else {
+ memcpy(this, &o, o_size);
+ memset(((uint8_t*)this) + o_size, 0, sizeof(ResTable_config) - o_size);
+ }
+ this->size = sizeof(*this);
+ }
+
+ void copyFromDtoH(const ResTable_config& o) {
+ if constexpr (kDeviceEndiannessSame) {
+ copyFromDeviceNoSwap(o);
+ } else {
+ copyFromDtoH_slow(o);
+ }
+ }
+
+ void swapHtoD() {
+ if constexpr (kDeviceEndiannessSame) {
+ ; // noop
+ } else {
+ swapHtoD_slow();
+ }
+ }
int compare(const ResTable_config& o) const;
int compareLogical(const ResTable_config& o) const;
@@ -1384,6 +1416,10 @@ struct ResTable_config
bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
String8 toString() const;
+
+ private:
+ void copyFromDtoH_slow(const ResTable_config& o);
+ void swapHtoD_slow();
};
/**
diff --git a/libs/androidfw/include/androidfw/misc.h b/libs/androidfw/include/androidfw/misc.h
index c9ba8a01a5e9..d8ca64a174a2 100644
--- a/libs/androidfw/include/androidfw/misc.h
+++ b/libs/androidfw/include/androidfw/misc.h
@@ -15,6 +15,7 @@
*/
#pragma once
+#include <sys/stat.h>
#include <time.h>
//
@@ -64,10 +65,15 @@ ModDate getFileModDate(const char* fileName);
/* same, but also returns -1 if the file has already been deleted */
ModDate getFileModDate(int fd);
+// Extract the modification date from the stat structure.
+ModDate getModDate(const struct ::stat& st);
+
// Check if |path| or |fd| resides on a readonly filesystem.
bool isReadonlyFilesystem(const char* path);
bool isReadonlyFilesystem(int fd);
+bool isKnownWritablePath(const char* path);
+
} // namespace android
// Whoever uses getFileModDate() will need this as well
diff --git a/libs/androidfw/misc.cpp b/libs/androidfw/misc.cpp
index 32f3624a3aee..26eb320805c9 100644
--- a/libs/androidfw/misc.cpp
+++ b/libs/androidfw/misc.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "misc"
-//
-// Miscellaneous utility functions.
-//
-#include <androidfw/misc.h>
+#include "androidfw/misc.h"
+
+#include <errno.h>
+#include <sys/stat.h>
#include "android-base/logging.h"
@@ -28,9 +28,7 @@
#include <sys/vfs.h>
#endif // __linux__
-#include <errno.h>
-#include <sys/stat.h>
-
+#include <array>
#include <cstdio>
#include <cstring>
#include <tuple>
@@ -40,28 +38,26 @@ namespace android {
/*
* Get a file's type.
*/
-FileType getFileType(const char* fileName)
-{
- struct stat sb;
-
- if (stat(fileName, &sb) < 0) {
- if (errno == ENOENT || errno == ENOTDIR)
- return kFileTypeNonexistent;
- else {
- PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
- return kFileTypeUnknown;
- }
- } else {
- if (S_ISREG(sb.st_mode))
- return kFileTypeRegular;
- else if (S_ISDIR(sb.st_mode))
- return kFileTypeDirectory;
- else if (S_ISCHR(sb.st_mode))
- return kFileTypeCharDev;
- else if (S_ISBLK(sb.st_mode))
- return kFileTypeBlockDev;
- else if (S_ISFIFO(sb.st_mode))
- return kFileTypeFifo;
+FileType getFileType(const char* fileName) {
+ struct stat sb;
+ if (stat(fileName, &sb) < 0) {
+ if (errno == ENOENT || errno == ENOTDIR)
+ return kFileTypeNonexistent;
+ else {
+ PLOG(ERROR) << "getFileType(): stat(" << fileName << ") failed";
+ return kFileTypeUnknown;
+ }
+ } else {
+ if (S_ISREG(sb.st_mode))
+ return kFileTypeRegular;
+ else if (S_ISDIR(sb.st_mode))
+ return kFileTypeDirectory;
+ else if (S_ISCHR(sb.st_mode))
+ return kFileTypeCharDev;
+ else if (S_ISBLK(sb.st_mode))
+ return kFileTypeBlockDev;
+ else if (S_ISFIFO(sb.st_mode))
+ return kFileTypeFifo;
#if defined(S_ISLNK)
else if (S_ISLNK(sb.st_mode))
return kFileTypeSymlink;
@@ -75,7 +71,7 @@ FileType getFileType(const char* fileName)
}
}
-static ModDate getModDate(const struct stat& st) {
+ModDate getModDate(const struct stat& st) {
#ifdef _WIN32
return st.st_mtime;
#elif defined(__APPLE__)
@@ -113,8 +109,14 @@ bool isReadonlyFilesystem(const char*) {
bool isReadonlyFilesystem(int) {
return false;
}
+bool isKnownWritablePath(const char*) {
+ return false;
+}
#else // __linux__
bool isReadonlyFilesystem(const char* path) {
+ if (isKnownWritablePath(path)) {
+ return false;
+ }
struct statfs sfs;
if (::statfs(path, &sfs)) {
PLOG(ERROR) << "isReadonlyFilesystem(): statfs(" << path << ") failed";
@@ -131,6 +133,13 @@ bool isReadonlyFilesystem(int fd) {
}
return (sfs.f_flags & ST_RDONLY) != 0;
}
+
+bool isKnownWritablePath(const char* path) {
+ // We know that all paths in /data/ are writable.
+ static constexpr char kRwPrefix[] = "/data/";
+ return strncmp(kRwPrefix, path, std::size(kRwPrefix) - 1) == 0;
+}
+
#endif // __linux__
} // namespace android
diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp
index cb2e56f5f5e4..22b9e69500d9 100644
--- a/libs/androidfw/tests/Idmap_test.cpp
+++ b/libs/androidfw/tests/Idmap_test.cpp
@@ -218,10 +218,11 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
auto apk_assets = ApkAssets::LoadOverlay(temp_file.path);
ASSERT_NE(nullptr, apk_assets);
- ASSERT_TRUE(apk_assets->IsUpToDate());
+ ASSERT_TRUE(apk_assets->IsOverlay());
+ ASSERT_EQ(UpToDate::True, apk_assets->IsUpToDate());
unlink(temp_file.path);
- ASSERT_FALSE(apk_assets->IsUpToDate());
+ ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
const auto sleep_duration =
std::chrono::nanoseconds(std::max(kModDateResolutionNs, 1'000'000ull));
@@ -230,7 +231,27 @@ TEST_F(IdmapTest, OverlayAssetsIsUpToDate) {
base::WriteStringToFile("hello", temp_file.path);
std::this_thread::sleep_for(sleep_duration);
- ASSERT_FALSE(apk_assets->IsUpToDate());
+ ASSERT_EQ(UpToDate::False, apk_assets->IsUpToDate());
+}
+
+TEST(IdmapTestUpToDate, Combine) {
+ ASSERT_EQ(UpToDate::False, combine(UpToDate::False, [] {
+ ADD_FAILURE(); // Shouldn't get called at all.
+ return UpToDate::False;
+ }));
+
+ ASSERT_EQ(UpToDate::False, combine(UpToDate::True, [] { return UpToDate::False; }));
+
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::True; }));
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::True, [] { return UpToDate::Always; }));
+ ASSERT_EQ(UpToDate::True, combine(UpToDate::Always, [] { return UpToDate::True; }));
+
+ ASSERT_EQ(UpToDate::Always, combine(UpToDate::Always, [] { return UpToDate::Always; }));
+}
+
+TEST(IdmapTestUpToDate, FromBool) {
+ ASSERT_EQ(UpToDate::False, fromBool(false));
+ ASSERT_EQ(UpToDate::True, fromBool(true));
}
} // namespace
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 84f59b5d01ae..c9625c405faa 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5866,14 +5866,21 @@ final public class MediaCodec {
@NonNull MediaCodec codec, @NonNull MediaFormat format);
/**
- * Called when the metrics for this codec have been flushed due to the
- * start of a new subsession.
+ * Called when the metrics for this codec have been flushed "mid-stream"
+ * due to the start of a new subsession during execution.
* <p>
- * This can happen when the codec is reconfigured after stop(), or
- * mid-stream e.g. if the video size changes. When this happens, the
- * metrics for the previous subsession are flushed, and
- * {@link MediaCodec#getMetrics} will return the metrics for the
- * new subsession. This happens just before the {@link Callback#onOutputFormatChanged}
+ * A new codec subsession normally starts when the codec is reconfigured
+ * after stop(), but it can also happen mid-stream e.g. if the video size
+ * changes. When this happens, the metrics for the previous subsession
+ * are flushed, and {@link MediaCodec#getMetrics} will return the metrics
+ * for the new subsession.
+ * <p>
+ * For subsessions that begin due to a reconfiguration, the metrics for
+ * the prior subsession can be retrieved via {@link MediaCodec#getMetrics}
+ * prior to calling {@link #configure}.
+ * <p>
+ * When a new subsession begins "mid-stream", the metrics for the prior
+ * subsession are flushed just before the {@link Callback#onOutputFormatChanged}
* event, so this <b>optional</b> callback is provided to be able to
* capture the final metrics for the previous subsession.
*
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 3d0c4069e782..213bc0673da6 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -27,6 +27,7 @@ import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
+import android.annotation.WorkerThread;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
@@ -475,6 +476,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public int loadSoundModel(@NonNull SoundModel soundModel) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -518,6 +520,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public int startRecognition(@NonNull UUID soundModelId, @Nullable Bundle params,
@NonNull ComponentName detectionService, @NonNull RecognitionConfig config) {
Objects.requireNonNull(soundModelId);
@@ -544,6 +547,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public int stopRecognition(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -568,6 +572,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public int unloadSoundModel(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
@@ -587,6 +592,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public boolean isRecognitionActive(@NonNull UUID soundModelId) {
if (soundModelId == null || mSoundTriggerSession == null) {
return false;
@@ -624,6 +630,7 @@ public final class SoundTriggerManager {
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
@UnsupportedAppUsage
@FlaggedApi(Flags.FLAG_MANAGER_API)
+ @WorkerThread
public int getModelState(@NonNull UUID soundModelId) {
if (mSoundTriggerSession == null) {
throw new IllegalStateException("No underlying SoundTriggerModule available");
diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
index afece5fac0fb..40a786ed560b 100644
--- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
+++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml
@@ -81,7 +81,9 @@
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/device_list"
android:layout_width="match_parent"
- android:layout_height="200dp"
+ android:layout_height="wrap_content"
+ app:layout_constraintHeight_max="220dp"
+ app:layout_constraintHeight_min="200dp"
android:scrollbars="vertical"
android:visibility="gone" />
diff --git a/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm b/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm
new file mode 100644
index 000000000000..b384a2418ff2
--- /dev/null
+++ b/packages/InputDevices/res/raw/keyboard_layout_romanian.kcm
@@ -0,0 +1,357 @@
+# 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.
+
+#
+# Romanian keyboard layout.
+#
+
+type OVERLAY
+
+map key 86 PLUS
+
+### ROW 1
+
+key GRAVE {
+ label: '\u201e'
+ base: '\u201e'
+ shift: '\u201d'
+ ralt: '`'
+ ralt+shift: '~'
+}
+
+key 1 {
+ label: '1'
+ base: '1'
+ shift: '!'
+ ralt: '\u0303'
+}
+
+key 2 {
+ label: '2'
+ base: '2'
+ shift: '@'
+ ralt: '\u030C'
+}
+
+key 3 {
+ label: '3'
+ base: '3'
+ shift: '#'
+ ralt: '\u0302'
+}
+
+key 4 {
+ label: '4'
+ base: '4'
+ shift: '$'
+ ralt: '\u0306'
+}
+
+key 5 {
+ label: '5'
+ base: '5'
+ shift: '%'
+ ralt: '\u030A'
+}
+
+key 6 {
+ label: '6'
+ base: '6'
+ shift: '^'
+ ralt: '\u0328'
+}
+
+key 7 {
+ label: '7'
+ base: '7'
+ shift: '&'
+ ralt: '\u0300'
+}
+
+key 8 {
+ label: '8'
+ base: '8'
+ shift: '*'
+ ralt: '\u0307'
+}
+
+key 9 {
+ label: '9'
+ base: '9'
+ shift: '('
+ ralt: '\u0301'
+}
+
+key 0 {
+ label: '0'
+ base: '0'
+ shift: ')'
+ ralt: '\u030B'
+}
+
+key MINUS {
+ label: '-'
+ base: '-'
+ shift: '_'
+ ralt: '\u0308'
+ ralt+shift: '\u2013'
+}
+
+key EQUALS {
+ label: '='
+ base: '='
+ shift: '+'
+ ralt: '\u0327'
+ ralt+shift: '\u00b1'
+}
+
+### ROW 2
+
+key Q {
+ label: 'Q'
+ base, capslock+shift: 'q'
+ shift, capslock: 'Q'
+}
+
+key W {
+ label: 'W'
+ base, capslock+shift: 'w'
+ shift, capslock: 'W'
+}
+
+key E {
+ label: 'E'
+ base, capslock+shift: 'e'
+ shift, capslock: 'E'
+ ralt: '\u20ac'
+}
+
+key R {
+ label: 'R'
+ base, capslock+shift: 'r'
+ shift, capslock: 'R'
+}
+
+key T {
+ label: 'T'
+ base, capslock+shift: 't'
+ shift, capslock: 'T'
+}
+
+key Y {
+ label: 'Y'
+ base, capslock+shift: 'y'
+ shift, capslock: 'Y'
+}
+
+key U {
+ label: 'U'
+ base, capslock+shift: 'u'
+ shift, capslock: 'U'
+}
+
+key I {
+ label: 'I'
+ base, capslock+shift: 'i'
+ shift, capslock: 'I'
+}
+
+key O {
+ label: 'O'
+ base, capslock+shift: 'o'
+ shift, capslock: 'O'
+}
+
+key P {
+ label: 'P'
+ base, capslock+shift: 'p'
+ shift, capslock: 'P'
+ ralt: '\u00a7'
+}
+
+key LEFT_BRACKET {
+ label: '\u0102'
+ base, capslock+shift: '\u0103'
+ shift, capslock: '\u0102'
+ ralt: '['
+ ralt+shift: '{'
+}
+
+key RIGHT_BRACKET {
+ label: '\u00ce'
+ base, capslock+shift: '\u00ee'
+ shift, capslock: '\u00ce'
+ ralt: ']'
+ ralt+shift: '}'
+}
+
+### ROW 3
+
+key A {
+ label: 'A'
+ base, capslock+shift: 'a'
+ shift, capslock: 'A'
+}
+
+key S {
+ label: 'S'
+ base, capslock+shift: 's'
+ shift, capslock: 'S'
+ ralt: '\u00df'
+}
+
+key D {
+ label: 'D'
+ base, capslock+shift: 'd'
+ shift, capslock: 'D'
+ ralt: '\u0111'
+ ralt+shift, ralt+capslock: '\u0110'
+ ralt+shift+capslock: '\u0111'
+}
+
+key F {
+ label: 'F'
+ base, capslock+shift: 'f'
+ shift, capslock: 'F'
+}
+
+key G {
+ label: 'G'
+ base, capslock+shift: 'g'
+ shift, capslock: 'G'
+}
+
+key H {
+ label: 'H'
+ base, capslock+shift: 'h'
+ shift, capslock: 'H'
+}
+
+key J {
+ label: 'J'
+ base, capslock+shift: 'j'
+ shift, capslock: 'J'
+}
+
+key K {
+ label: 'K'
+ base, capslock+shift: 'k'
+ shift, capslock: 'K'
+}
+
+key L {
+ label: 'L'
+ base, capslock+shift: 'l'
+ shift, capslock: 'L'
+ ralt: '\u0142'
+ ralt+shift, ralt+capslock: '\u0141'
+ ralt+shift+capslock: '\u0142'
+}
+
+key SEMICOLON {
+ label: '\u0218'
+ base, capslock+shift: '\u0219'
+ shift, capslock: '\u0218'
+ ralt: ';'
+ ralt+shift: ':'
+}
+
+key APOSTROPHE {
+ label: '\u021a'
+ base, capslock+shift: '\u021b'
+ shift, capslock: '\u021a'
+ ralt: '\''
+ ralt+shift: '\u0022'
+}
+
+key BACKSLASH {
+ label: '\u00c2'
+ base, capslock+shift: '\u00e2'
+ shift, capslock: '\u00c2'
+ ralt: '\\'
+ ralt+shift: '|'
+}
+
+### ROW 4
+
+key PLUS {
+ label: '\\'
+ base: '\\'
+ shift: '|'
+}
+
+key Z {
+ label: 'Z'
+ base, capslock+shift: 'z'
+ shift, capslock: 'Z'
+}
+
+key X {
+ label: 'X'
+ base, capslock+shift: 'x'
+ shift, capslock: 'X'
+}
+
+key C {
+ label: 'C'
+ base, capslock+shift: 'c'
+ shift, capslock: 'C'
+ ralt: '\u00a9'
+}
+
+key V {
+ label: 'V'
+ base, capslock+shift: 'v'
+ shift, capslock: 'V'
+}
+
+key B {
+ label: 'B'
+ base, capslock+shift: 'b'
+ shift, capslock: 'B'
+}
+
+key N {
+ label: 'N'
+ base, capslock+shift: 'n'
+ shift, capslock: 'N'
+}
+
+key M {
+ label: 'M'
+ base, capslock+shift: 'm'
+ shift, capslock: 'M'
+}
+
+key COMMA {
+ label: ','
+ base: ','
+ shift: ';'
+ ralt: '<'
+ ralt+shift: '\u00ab'
+}
+
+key PERIOD {
+ label: '.'
+ base: '.'
+ shift: ':'
+ ralt: '>'
+ ralt+shift: '\u00bb'
+}
+
+key SLASH {
+ label: '/'
+ base: '/'
+ shift: '?'
+}
diff --git a/packages/InputDevices/res/values/strings.xml b/packages/InputDevices/res/values/strings.xml
index 5a911256d9be..bd7cdc481524 100644
--- a/packages/InputDevices/res/values/strings.xml
+++ b/packages/InputDevices/res/values/strings.xml
@@ -164,4 +164,7 @@
<!-- Montenegrin (Cyrillic) keyboard layout label. [CHAR LIMIT=35] -->
<string name="keyboard_layout_montenegrin_cyrillic">Montenegrin (Cyrillic)</string>
+
+ <!-- Romanian keyboard layout label. [CHAR LIMIT=35] -->
+ <string name="keyboard_layout_romanian">Romanian</string>
</resources>
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 93094890418d..9ce9a87a1f9f 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -360,4 +360,11 @@
android:keyboardLayout="@raw/keyboard_layout_serbian_and_montenegrin_cyrillic"
android:keyboardLocale="cnr-Cyrl-ME"
android:keyboardLayoutType="extended" />
+
+ <keyboard-layout
+ android:name="keyboard_layout_romanian"
+ android:label="@string/keyboard_layout_romanian"
+ android:keyboardLayout="@raw/keyboard_layout_romanian"
+ android:keyboardLocale="ro-Latn-RO"
+ android:keyboardLayoutType="qwerty" />
</keyboard-layouts>
diff --git a/packages/SettingsLib/DataStore/OWNERS b/packages/SettingsLib/DataStore/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/DataStore/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Graph/OWNERS b/packages/SettingsLib/Graph/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Graph/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
index 1ed814a2ae20..51813a1c9aab 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -69,7 +69,6 @@ constructor(
val visitedScreens: Set<String> = setOf(),
val locale: Locale? = null,
val flags: Int = PreferenceGetterFlags.ALL,
- val includeValue: Boolean = true, // TODO: clean up
val includeValueDescriptor: Boolean = true,
)
diff --git a/packages/SettingsLib/Ipc/OWNERS b/packages/SettingsLib/Ipc/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Ipc/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Metadata/OWNERS b/packages/SettingsLib/Metadata/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Metadata/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/OWNERS_catalyst b/packages/SettingsLib/OWNERS_catalyst
new file mode 100644
index 000000000000..d44ac68585a2
--- /dev/null
+++ b/packages/SettingsLib/OWNERS_catalyst
@@ -0,0 +1,9 @@
+# OWNERS of Catalyst libraries (DataStore, Metadata, etc.)
+
+# Main developers
+jiannan@google.com
+cechkahn@google.com
+sunnyshao@google.com
+
+# Emergency only
+cipson@google.com
diff --git a/packages/SettingsLib/Preference/OWNERS b/packages/SettingsLib/Preference/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Preference/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/Service/OWNERS b/packages/SettingsLib/Service/OWNERS
new file mode 100644
index 000000000000..1219dc4aa606
--- /dev/null
+++ b/packages/SettingsLib/Service/OWNERS
@@ -0,0 +1 @@
+include ../OWNERS_catalyst
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt
new file mode 100644
index 000000000000..5b7e2a86135a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatter.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.Context
+import android.content.res.Resources
+import android.icu.text.DecimalFormat
+import android.icu.text.MeasureFormat
+import android.icu.text.NumberFormat
+import android.icu.text.UnicodeSet
+import android.icu.text.UnicodeSetSpanner
+import android.icu.util.Measure
+import android.text.format.Formatter
+import android.text.format.Formatter.RoundedBytesResult
+import java.math.BigDecimal
+
+class BytesFormatter(resources: Resources) {
+
+ enum class UseCase(val flag: Int) {
+ FileSize(Formatter.FLAG_SI_UNITS),
+ DataUsage(Formatter.FLAG_IEC_UNITS),
+ }
+
+ data class Result(val number: String, val units: String)
+
+ constructor(context: Context) : this(context.resources)
+
+ private val locale = resources.configuration.locales[0]
+
+ fun format(bytes: Long, useCase: UseCase): String {
+ val rounded = RoundedBytesResult.roundBytes(bytes, useCase.flag)
+ val numberFormatter = getNumberFormatter(rounded.fractionDigits)
+ return numberFormatter.formatRoundedBytesResult(rounded)
+ }
+
+ fun formatWithUnits(bytes: Long, useCase: UseCase): Result {
+ val rounded = RoundedBytesResult.roundBytes(bytes, useCase.flag)
+ val numberFormatter = getNumberFormatter(rounded.fractionDigits)
+ val formattedString = numberFormatter.formatRoundedBytesResult(rounded)
+ val formattedNumber = numberFormatter.format(rounded.value)
+ return Result(
+ number = formattedNumber,
+ units = formattedString.removeFirst(formattedNumber),
+ )
+ }
+
+ private fun NumberFormat.formatRoundedBytesResult(rounded: RoundedBytesResult): String {
+ val measureFormatter =
+ MeasureFormat.getInstance(locale, MeasureFormat.FormatWidth.SHORT, this)
+ return measureFormatter.format(Measure(rounded.value, rounded.units))
+ }
+
+ private fun getNumberFormatter(fractionDigits: Int) =
+ NumberFormat.getInstance(locale).apply {
+ minimumFractionDigits = fractionDigits
+ maximumFractionDigits = fractionDigits
+ isGroupingUsed = false
+ if (this is DecimalFormat) {
+ setRoundingMode(BigDecimal.ROUND_HALF_UP)
+ }
+ }
+
+ private companion object {
+ fun String.removeFirst(removed: String): String =
+ SPACES_AND_CONTROLS.trim(replaceFirst(removed, "")).toString()
+
+ val SPACES_AND_CONTROLS = UnicodeSetSpanner(UnicodeSet("[[:Zs:][:Cf:]]").freeze())
+ }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
new file mode 100644
index 000000000000..7220848eebff
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BytesFormatterTest.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class BytesFormatterTest {
+
+ private val context: Context = ApplicationProvider.getApplicationContext()
+
+ private val formatter = BytesFormatter(context)
+
+ @Test
+ fun `Zero bytes`() {
+ // Given a byte value of 0, the formatted output should be "0 byte" for both FileSize
+ // and DataUsage UseCases. This verifies special handling of zero values.
+
+ val fileSizeResult = formatter.format(0, BytesFormatter.UseCase.FileSize)
+ assertThat(fileSizeResult).isEqualTo("0 byte")
+
+ val dataUsageResult = formatter.format(0, BytesFormatter.UseCase.DataUsage)
+ assertThat(dataUsageResult).isEqualTo("0 byte")
+ }
+
+ @Test
+ fun `Positive bytes`() {
+ // Given a positive byte value (e.g., 1000), the formatted output should be correctly
+ // displayed with appropriate units (e.g., '1.00 kB') for both UseCases.
+
+ val fileSizeResult = formatter.format(1000, BytesFormatter.UseCase.FileSize)
+ assertThat(fileSizeResult).isEqualTo("1.00 kB")
+
+ val dataUsageResult = formatter.format(1024, BytesFormatter.UseCase.DataUsage)
+ assertThat(dataUsageResult).isEqualTo("1.00 kB")
+ }
+
+ @Test
+ fun `Large bytes`() {
+ // Given a very large byte value (e.g., Long.MAX_VALUE), the formatted output should be
+ // correctly displayed with the largest unit (e.g., 'PB') for both UseCases.
+
+ val fileSizeResult = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.FileSize)
+ assertThat(fileSizeResult).isEqualTo("9223 PB")
+
+ val dataUsageResult = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.DataUsage)
+ assertThat(dataUsageResult).isEqualTo("8192 PB")
+ }
+
+ @Test
+ fun `Bytes requiring rounding`() {
+ // Given byte values that require rounding (e.g., 1512), the formatted output should be
+ // rounded to the appropriate number of decimal places (e.g., '1.51 kB').
+
+ val fileSizeResult = formatter.format(1512, BytesFormatter.UseCase.FileSize)
+ assertThat(fileSizeResult).isEqualTo("1.51 kB")
+
+ val dataUsageResult = formatter.format(1512, BytesFormatter.UseCase.DataUsage)
+ assertThat(dataUsageResult).isEqualTo("1.48 kB")
+ }
+
+ @Test
+ fun `FileSize UseCase`() {
+ // When the UseCase is FileSize, the correct units (byte, KB, kB, GB, TB, PB) should
+ // be used.
+ val values =
+ listOf(
+ 1L,
+ 1024L,
+ 1024L * 1024L,
+ 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L * 1024L * 1024L,
+ )
+ val expectedUnits = listOf("byte", "kB", "MB", "GB", "TB", "PB", "PB")
+
+ values.zip(expectedUnits).forEach { (value, expectedUnit) ->
+ val result = formatter.format(value, BytesFormatter.UseCase.FileSize)
+ assertThat(result).contains(expectedUnit)
+ }
+ }
+
+ @Test
+ fun `DataUsage UseCase`() {
+ // When the UseCase is DataUsage, the correct units (byte, kB, MB, GB, TB, PB) should
+ // be used.
+ val values =
+ listOf(
+ 1L,
+ 1024L,
+ 1024L * 1024L,
+ 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L * 1024L,
+ 1024L * 1024L * 1024L * 1024L * 1024L * 1024L,
+ )
+ val expectedUnits = listOf("byte", "kB", "MB", "GB", "TB", "PB", "PB")
+
+ values.zip(expectedUnits).forEach { (value, expectedUnit) ->
+ val result = formatter.format(value, BytesFormatter.UseCase.DataUsage)
+ assertThat(result).contains(expectedUnit)
+ }
+ }
+
+ @Test
+ fun `Fraction digits`() {
+ // The number of fraction digits in the output should be correctly determined based on
+ // the rounded byte value.
+
+ assertThat(formatter.format(1500, BytesFormatter.UseCase.FileSize)).isEqualTo("1.50 kB")
+ assertThat(formatter.format(1050, BytesFormatter.UseCase.FileSize)).isEqualTo("1.05 kB")
+ assertThat(formatter.format(999, BytesFormatter.UseCase.FileSize)).isEqualTo("1.00 kB")
+ }
+
+ @Test
+ fun `Rounding mode`() {
+ // The rounding mode used for formatting should be ROUND_HALF_UP.
+
+ val result = formatter.format(1006, BytesFormatter.UseCase.FileSize)
+
+ assertThat(result).isEqualTo("1.01 kB") // Ensure rounding mode is effective
+ }
+
+ @Test
+ fun `Grouping separator`() {
+ // Grouping separators should not be used in the formatted output.
+
+ val result = formatter.format(Long.MAX_VALUE, BytesFormatter.UseCase.FileSize)
+
+ assertThat(result).isEqualTo("9223 PB")
+ }
+
+ @Test
+ fun `Format with units`() {
+ // Verify that the `formatWithUnits` method correctly formats the given bytes with the
+ // specified units.
+
+ val resultByte = formatter.formatWithUnits(0, BytesFormatter.UseCase.FileSize)
+ assertThat(resultByte).isEqualTo(BytesFormatter.Result("0", "byte"))
+
+ val resultKb = formatter.formatWithUnits(1000, BytesFormatter.UseCase.FileSize)
+ assertThat(resultKb).isEqualTo(BytesFormatter.Result("1.00", "kB"))
+
+ val resultMb = formatter.formatWithUnits(479_999_999, BytesFormatter.UseCase.FileSize)
+ assertThat(resultMb).isEqualTo(BytesFormatter.Result("480", "MB"))
+
+ val resultGb = formatter.formatWithUnits(20_100_000_000, BytesFormatter.UseCase.FileSize)
+ assertThat(resultGb).isEqualTo(BytesFormatter.Result("20.10", "GB"))
+
+ val resultTb =
+ formatter.formatWithUnits(300_100_000_000_000, BytesFormatter.UseCase.FileSize)
+ assertThat(resultTb).isEqualTo(BytesFormatter.Result("300", "TB"))
+
+ val resultPb =
+ formatter.formatWithUnits(1000_000_000_000_000, BytesFormatter.UseCase.FileSize)
+ assertThat(resultPb).isEqualTo(BytesFormatter.Result("1.00", "PB"))
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 145b62cd12b5..68e9fe703090 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -73,6 +73,10 @@ public class BluetoothUtils {
private static final Set<Integer> SA_PROFILES =
ImmutableSet.of(
BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID);
+ private static final List<Integer> BLUETOOTH_DEVICE_CLASS_HEADSET =
+ List.of(
+ BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES,
+ BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET);
private static final String TEMP_BOND_TYPE = "TEMP_BOND_TYPE";
private static final String TEMP_BOND_DEVICE_METADATA_VALUE = "le_audio_sharing";
@@ -390,6 +394,19 @@ public class BluetoothUtils {
return false;
}
+ /** Checks whether the bluetooth device is a headset. */
+ public static boolean isHeadset(@NonNull BluetoothDevice bluetoothDevice) {
+ String deviceType =
+ BluetoothUtils.getStringMetaData(
+ bluetoothDevice, BluetoothDevice.METADATA_DEVICE_TYPE);
+ if (!TextUtils.isEmpty(deviceType)) {
+ return BluetoothDevice.DEVICE_TYPE_HEADSET.equals(deviceType)
+ || BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.equals(deviceType);
+ }
+ BluetoothClass btClass = bluetoothDevice.getBluetoothClass();
+ return btClass != null && BLUETOOTH_DEVICE_CLASS_HEADSET.contains(btClass.getDeviceClass());
+ }
+
/** Create an Icon pointing to a drawable. */
public static IconCompat createIconWithDrawable(Drawable drawable) {
Bitmap bitmap;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index d49447f05011..cafe19ff9a9b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -80,7 +80,9 @@ public class BluetoothUtilsTest {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private CachedBluetoothDevice mCachedBluetoothDevice;
- @Mock private BluetoothDevice mBluetoothDevice;
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private BluetoothDevice mBluetoothDevice;
+
@Mock private AudioManager mAudioManager;
@Mock private PackageManager mPackageManager;
@Mock private LeAudioProfile mA2dpProfile;
@@ -399,6 +401,38 @@ public class BluetoothUtilsTest {
}
@Test
+ public void isHeadset_metadataMatched_returnTrue() {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET.getBytes());
+
+ assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isHeadset_metadataNotMatched_returnFalse() {
+ when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_DEVICE_TYPE))
+ .thenReturn(BluetoothDevice.DEVICE_TYPE_CARKIT.getBytes());
+
+ assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
+ public void isHeadset_btClassMatched_returnTrue() {
+ when(mBluetoothDevice.getBluetoothClass().getDeviceClass())
+ .thenReturn(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+
+ assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isTrue();
+ }
+
+ @Test
+ public void isHeadset_btClassNotMatched_returnFalse() {
+ when(mBluetoothDevice.getBluetoothClass().getDeviceClass())
+ .thenReturn(BluetoothClass.Device.AUDIO_VIDEO_LOUDSPEAKER);
+
+ assertThat(BluetoothUtils.isHeadset(mBluetoothDevice)).isFalse();
+ }
+
+ @Test
public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
diff --git a/packages/SettingsProvider/res/values/defaults.xml b/packages/SettingsProvider/res/values/defaults.xml
index 5ddf005d9468..dafcc729b8f1 100644
--- a/packages/SettingsProvider/res/values/defaults.xml
+++ b/packages/SettingsProvider/res/values/defaults.xml
@@ -322,9 +322,6 @@
<!-- Whether vibrate icon is shown in the status bar by default. -->
<integer name="def_statusBarVibrateIconEnabled">0</integer>
- <!-- Whether predictive back animation is enabled by default. -->
- <bool name="def_enable_back_animation">false</bool>
-
<!-- Whether wifi is always requested by default. -->
<bool name="def_enable_wifi_always_requested">false</bool>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 1fc1f05ae149..dd28402d705f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -91,6 +91,7 @@ public class SecureSettings {
Settings.Secure.KEY_REPEAT_TIMEOUT_MS,
Settings.Secure.KEY_REPEAT_DELAY_MS,
Settings.Secure.CAMERA_GESTURE_DISABLED,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY,
Settings.Secure.ACCESSIBILITY_LARGE_POINTER_ICON,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 5b4ee8bdb339..1f56f10cca7d 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -109,6 +109,7 @@ public class SystemSettings {
Settings.System.LOCALE_PREFERENCES,
Settings.System.MOUSE_REVERSE_VERTICAL_SCROLLING,
Settings.System.MOUSE_SCROLLING_ACCELERATION,
+ Settings.System.MOUSE_SCROLLING_SPEED,
Settings.System.MOUSE_SWAP_PRIMARY_BUTTON,
Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED,
Settings.System.TOUCHPAD_POINTER_SPEED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index d0e88d5d6a3c..b01f6229af16 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -140,6 +140,8 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.KEY_REPEAT_TIMEOUT_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.KEY_REPEAT_DELAY_MS, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_GESTURE_DISABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(
+ Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_AUTOCLICK_DELAY, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.ACCESSIBILITY_LARGE_POINTER_ICON, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 0432eeacec4d..4d98a11bdfe7 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -227,6 +227,7 @@ public class SystemSettingsValidators {
VALIDATORS.put(System.MOUSE_SWAP_PRIMARY_BUTTON, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.MOUSE_SCROLLING_ACCELERATION, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.MOUSE_POINTER_ACCELERATION_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(System.MOUSE_SCROLLING_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_POINTER_SPEED, new InclusiveIntegerRangeValidator(-7, 7));
VALIDATORS.put(System.TOUCHPAD_NATURAL_SCROLLING, BOOLEAN_VALIDATOR);
VALIDATORS.put(System.TOUCHPAD_TAP_TO_CLICK, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index a2cc008843a4..ef0bc3b100e0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -193,6 +193,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
"power_button_instantly_locks";
private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY =
"pin_enhanced_privacy";
+ private static final int NUM_LOCK_SETTINGS = 5;
// Error messages for logging metrics.
private static final String ERROR_COULD_NOT_READ_FROM_CURSOR =
@@ -208,6 +209,13 @@ public class SettingsBackupAgent extends BackupAgentHelper {
private static final String ERROR_SKIPPED_DUE_TO_LARGE_SCREEN =
"skipped_due_to_large_screen";
private static final String ERROR_DID_NOT_PASS_VALIDATION = "did_not_pass_validation";
+ private static final String ERROR_IO_EXCEPTION = "io_exception";
+ private static final String ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG =
+ "failed_to_restore_softap_config";
+ private static final String ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES =
+ "failed_to_convert_network_policies";
+ private static final String ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION =
+ "unknown_backup_serialization_version";
// Name of the temporary file we use during full backup/restore. This is
@@ -794,29 +802,44 @@ public class SettingsBackupAgent extends BackupAgentHelper {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
+ int backedUpSettingsCount = 0;
try {
out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO_ENABLED);
out.writeUTF(ownerInfoEnabled ? "1" : "0");
+ backedUpSettingsCount++;
if (ownerInfo != null) {
out.writeUTF(KEY_LOCK_SETTINGS_OWNER_INFO);
out.writeUTF(ownerInfo != null ? ownerInfo : "");
+ backedUpSettingsCount++;
}
if (lockPatternUtils.isVisiblePatternEverChosen(userId)) {
out.writeUTF(KEY_LOCK_SETTINGS_VISIBLE_PATTERN_ENABLED);
out.writeUTF(visiblePatternEnabled ? "1" : "0");
+ backedUpSettingsCount++;
}
if (lockPatternUtils.isPowerButtonInstantlyLocksEverChosen(userId)) {
out.writeUTF(KEY_LOCK_SETTINGS_POWER_BUTTON_INSTANTLY_LOCKS);
out.writeUTF(powerButtonInstantlyLocks ? "1" : "0");
+ backedUpSettingsCount++;
}
if (lockPatternUtils.isPinEnhancedPrivacyEverChosen(userId)) {
out.writeUTF(KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY);
out.writeUTF(lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) ? "1" : "0");
+ backedUpSettingsCount++;
}
// End marker
out.writeUTF("");
out.flush();
+ if (areAgentMetricsEnabled) {
+ numberOfSettingsPerKey.put(KEY_LOCK_SETTINGS, backedUpSettingsCount);
+ }
} catch (IOException ioe) {
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsBackupFailed(
+ KEY_LOCK_SETTINGS,
+ NUM_LOCK_SETTINGS - backedUpSettingsCount,
+ ERROR_IO_EXCEPTION);
+ }
}
return baos.toByteArray();
}
@@ -1162,6 +1185,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, nBytes);
DataInputStream in = new DataInputStream(bais);
+ int restoredLockSettingsCount = 0;
try {
String key;
// Read until empty string marker
@@ -1187,9 +1211,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
lockPatternUtils.setPinEnhancedPrivacyEnabled("1".equals(value), userId);
break;
}
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestored(KEY_LOCK_SETTINGS, /* count= */ 1);
+ restoredLockSettingsCount++;
+ }
+
}
in.close();
} catch (IOException ioe) {
+ if (areAgentMetricsEnabled) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ KEY_LOCK_SETTINGS,
+ NUM_LOCK_SETTINGS - restoredLockSettingsCount,
+ ERROR_IO_EXCEPTION);
+ }
}
}
@@ -1309,12 +1344,31 @@ public class SettingsBackupAgent extends BackupAgentHelper {
mWifiManager.restoreSupplicantBackupData(supplicant_bytes, ipconfig_bytes);
}
- private byte[] getSoftAPConfiguration() {
- return mWifiManager.retrieveSoftApBackupData();
+ @VisibleForTesting
+ byte[] getSoftAPConfiguration() {
+ byte[] data = mWifiManager.retrieveSoftApBackupData();
+ if (areAgentMetricsEnabled) {
+ // We're unable to determine how many settings this includes, so we'll just log 1.
+ numberOfSettingsPerKey.put(KEY_SOFTAP_CONFIG, 1);
+ }
+ return data;
}
- private void restoreSoftApConfiguration(byte[] data) {
- SoftApConfiguration configInCloud = mWifiManager.restoreSoftApBackupData(data);
+ @VisibleForTesting
+ void restoreSoftApConfiguration(byte[] data) {
+ SoftApConfiguration configInCloud;
+ if (areAgentMetricsEnabled) {
+ try {
+ configInCloud = mWifiManager.restoreSoftApBackupData(data);
+ mBackupRestoreEventLogger.logItemsRestored(KEY_SOFTAP_CONFIG, /* count= */ 1);
+ } catch (Exception e) {
+ configInCloud = null;
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ KEY_SOFTAP_CONFIG, /* count= */ 1, ERROR_FAILED_TO_RESTORE_SOFTAP_CONFIG);
+ }
+ } else {
+ configInCloud = mWifiManager.restoreSoftApBackupData(data);
+ }
if (configInCloud != null) {
if (DEBUG) Log.d(TAG, "Successfully unMarshaled SoftApConfiguration ");
// Depending on device hardware, we may need to notify the user of a setting change
@@ -1384,6 +1438,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
try {
out.writeInt(NETWORK_POLICIES_BACKUP_VERSION);
out.writeInt(policies.length);
+ int numberOfPoliciesBackedUp = 0;
for (NetworkPolicy policy : policies) {
// We purposefully only backup policies that the user has
// defined; any inferred policies might include
@@ -1393,13 +1448,23 @@ public class SettingsBackupAgent extends BackupAgentHelper {
out.writeByte(BackupUtils.NOT_NULL);
out.writeInt(marshaledPolicy.length);
out.write(marshaledPolicy);
+ if (areAgentMetricsEnabled) {
+ numberOfPoliciesBackedUp++;
+ }
} else {
out.writeByte(BackupUtils.NULL);
}
}
+ if (areAgentMetricsEnabled) {
+ numberOfSettingsPerKey.put(KEY_NETWORK_POLICIES, numberOfPoliciesBackedUp);
+ }
} catch (IOException ioe) {
Log.e(TAG, "Failed to convert NetworkPolicies to byte array " + ioe.getMessage());
baos.reset();
+ mBackupRestoreEventLogger.logItemsBackupFailed(
+ KEY_NETWORK_POLICIES,
+ policies.length,
+ ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
}
}
return baos.toByteArray();
@@ -1433,6 +1498,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
try {
int version = in.readInt();
if (version < 1 || version > NETWORK_POLICIES_BACKUP_VERSION) {
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ KEY_NETWORK_POLICIES,
+ /* count= */ 1,
+ ERROR_UNKNOWN_BACKUP_SERIALIZATION_VERSION);
throw new BackupUtils.BadVersionException(
"Unknown Backup Serialization Version");
}
@@ -1449,10 +1518,15 @@ public class SettingsBackupAgent extends BackupAgentHelper {
}
// Only set the policies if there was no error in the restore operation
networkPolicyManager.setNetworkPolicies(policies);
+ mBackupRestoreEventLogger.logItemsRestored(KEY_NETWORK_POLICIES, policies.length);
} catch (NullPointerException | IOException | BackupUtils.BadVersionException
| DateTimeException e) {
// NPE can be thrown when trying to instantiate a NetworkPolicy
Log.e(TAG, "Failed to convert byte array to NetworkPolicies " + e.getMessage());
+ mBackupRestoreEventLogger.logItemsRestoreFailed(
+ KEY_NETWORK_POLICIES,
+ /* count= */ 1,
+ ERROR_FAILED_TO_CONVERT_NETWORK_POLICIES);
}
}
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index dedd7ebd1ef7..5ad4b8a6dffe 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1715,6 +1715,9 @@ class SettingsProtoDumpUtil {
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
SecureSettingsProto.Accessibility.ENABLED_ACCESSIBILITY_SERVICES);
dumpSetting(s, p,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ SecureSettingsProto.Accessibility.AUTOCLICK_CURSOR_AREA_SIZE);
+ dumpSetting(s, p,
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED,
SecureSettingsProto.Accessibility.AUTOCLICK_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed193515b382..cb656bdd5d54 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -6122,17 +6122,7 @@ public class SettingsProvider extends ContentProvider {
}
if (currentVersion == 220) {
- final SettingsState globalSettings = getGlobalSettingsLocked();
- final Setting enableBackAnimation =
- globalSettings.getSettingLocked(Global.ENABLE_BACK_ANIMATION);
- if (enableBackAnimation.isNull()) {
- final boolean defEnableBackAnimation =
- getContext()
- .getResources()
- .getBoolean(R.bool.def_enable_back_animation);
- initGlobalSettingsDefaultValLocked(
- Settings.Global.ENABLE_BACK_ANIMATION, defEnableBackAnimation);
- }
+ // Version 221: Removed
currentVersion = 221;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index c88a7fd834d6..cbdb36fff98c 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -564,7 +564,6 @@ public class SettingsBackupTest {
Settings.Global.WATCHDOG_TIMEOUT_MILLIS,
Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER,
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
- Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only
Settings.Global.HEARING_DEVICE_LOCAL_AMBIENT_VOLUME, // cache per hearing device
Settings.Global.HEARING_DEVICE_LOCAL_NOTIFICATION, // cache per hearing device
Settings.Global.Wearable.COMBINED_LOCATION_ENABLE,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
index 18c43a704bcc..95dd0db40c0e 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsBackupAgentTest.java
@@ -16,6 +16,8 @@
package com.android.providers.settings;
+import static com.android.providers.settings.SettingsBackupRestoreKeys.KEY_SOFTAP_CONFIG;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
@@ -28,6 +30,7 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;
+import android.annotation.Nullable;
import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupDataInput;
@@ -42,6 +45,8 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
+import android.net.wifi.SoftApConfiguration;
+import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
@@ -126,6 +131,7 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
@Mock private BackupDataInput mBackupDataInput;
@Mock private BackupDataOutput mBackupDataOutput;
+ @Mock private static WifiManager mWifiManager;
private TestFriendlySettingsBackupAgent mAgentUnderTest;
private Context mContext;
@@ -754,6 +760,80 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
}
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void getSoftAPConfiguration_flagIsEnabled_numberOfSettingsInKeyAreRecorded() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null);
+
+ mAgentUnderTest.getSoftAPConfiguration();
+
+ assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void getSoftAPConfiguration_flagIsNotEnabled_numberOfSettingsInKeyAreNotRecorded() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
+ when(mWifiManager.retrieveSoftApBackupData()).thenReturn(null);
+
+ mAgentUnderTest.getSoftAPConfiguration();
+
+ assertEquals(mAgentUnderTest.getNumberOfSettingsPerKey(KEY_SOFTAP_CONFIG), 0);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void
+ restoreSoftApConfiguration_flagIsEnabled_restoreIsSuccessful_successMetricsAreLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build();
+ byte[] data = config.toString().getBytes();
+ when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null);
+
+ mAgentUnderTest.restoreSoftApConfiguration(data);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getSuccessCount(), 1);
+ }
+
+ @Test
+ @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void
+ restoreSoftApConfiguration_flagIsEnabled_restoreIsNotSuccessful_failureMetricsAreLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build();
+ byte[] data = config.toString().getBytes();
+ when(mWifiManager.restoreSoftApBackupData(any())).thenThrow(new RuntimeException());
+
+ mAgentUnderTest.restoreSoftApConfiguration(data);
+
+ DataTypeResult loggingResult =
+ getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest);
+ assertNotNull(loggingResult);
+ assertEquals(loggingResult.getFailCount(), 1);
+ }
+
+ @Test
+ @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
+ public void restoreSoftApConfiguration_flagIsNotEnabled_metricsAreNotLogged() {
+ mAgentUnderTest.onCreate(
+ UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.RESTORE);
+ SoftApConfiguration config = new SoftApConfiguration.Builder().setSsid("test").build();
+ byte[] data = config.toString().getBytes();
+ when(mWifiManager.restoreSoftApBackupData(any())).thenReturn(null);
+
+ mAgentUnderTest.restoreSoftApConfiguration(data);
+
+ assertNull(getLoggingResultForDatatype(KEY_SOFTAP_CONFIG, mAgentUnderTest));
+ }
+
private byte[] generateBackupData(Map<String, String> keyValueData) {
int totalBytes = 0;
for (String key : keyValueData.keySet()) {
@@ -890,6 +970,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
this.numberOfSettingsPerKey.put(key, numberOfSettings);
}
}
+
+ int getNumberOfSettingsPerKey(String key) {
+ if (numberOfSettingsPerKey == null || !numberOfSettingsPerKey.containsKey(key)) {
+ return 0;
+ }
+ return numberOfSettingsPerKey.get(key);
+ }
}
/** The TestSettingsHelper tracks which values have been backed up and/or restored. */
@@ -944,6 +1031,14 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
public ContentResolver getContentResolver() {
return mContentResolver;
}
+
+ @Override
+ public Object getSystemService(String name) {
+ if (name.equals(Context.WIFI_SERVICE)) {
+ return mWifiManager;
+ }
+ return super.getSystemService(name);
+ }
}
/** ContentProvider which returns a set of known test values. */
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 3cdaebc10d27..9adc95a01216 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -85,6 +85,9 @@ filegroup {
filegroup {
name: "SystemUI-tests-broken-robofiles-run",
srcs: [
+ "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
+ "tests/src/**/systemui/power/PowerNotificationWarningsTest.java",
+ "tests/src/**/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt",
"tests/src/**/systemui/dreams/touch/CommunalTouchHandlerTest.java",
"tests/src/**/systemui/shade/NotificationShadeWindowViewControllerTest.kt",
"tests/src/**/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt",
@@ -285,6 +288,7 @@ filegroup {
"tests/src/**/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java",
"tests/src/**/systemui/shared/system/RemoteTransitionTest.java",
"tests/src/**/systemui/qs/tiles/dialog/InternetDetailsContentControllerTest.java",
+ "tests/src/**/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt",
"tests/src/**/systemui/qs/external/TileLifecycleManagerTest.java",
"tests/src/**/systemui/ScreenDecorationsTest.java",
"tests/src/**/systemui/statusbar/policy/BatteryControllerStartableTest.java",
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 795b39576391..c6cc9a975191 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -119,4 +119,5 @@ xuqiu@google.com
yeinj@google.com
yuandizhou@google.com
yurilin@google.com
+yuzhechen@google.com
zakcohen@google.com
diff --git a/packages/SystemUI/aconfig/predictive_back.aconfig b/packages/SystemUI/aconfig/predictive_back.aconfig
index ad4a02764176..ee918c275b7b 100644
--- a/packages/SystemUI/aconfig/predictive_back.aconfig
+++ b/packages/SystemUI/aconfig/predictive_back.aconfig
@@ -7,17 +7,3 @@ flag {
description: "Enable Shade Animations"
bug: "327732946"
}
-
-flag {
- name: "predictive_back_animate_bouncer"
- namespace: "systemui"
- description: "Enable Predictive Back Animation in Bouncer"
- bug: "327733487"
-}
-
-flag {
- name: "predictive_back_animate_dialogs"
- namespace: "systemui"
- description: "Enable Predictive Back Animation for SysUI dialogs"
- bug: "327721544"
-}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 715d22328f2b..7d5fd903c01b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -133,14 +133,6 @@ flag {
}
flag {
- name: "notifications_footer_view_refactor"
- namespace: "systemui"
- description: "Enables the refactored version of the footer view in the notification shade "
- "(containing the \"Clear all\" button). Should not bring any behavior changes"
- bug: "293167744"
-}
-
-flag {
name: "notifications_icon_container_refactor"
namespace: "systemui"
description: "Enables the refactored version of the notification icon container in StatusBar, "
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
deleted file mode 100644
index 1c9dabbb0e07..000000000000
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/AnimationFeatureFlags.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.android.systemui.animation
-
-interface AnimationFeatureFlags {
- val isPredictiveBackQsDialogAnim: Boolean
- get() = false
-}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
index 907c39d842ce..c88c4ebb1a8d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogTransitionAnimator.kt
@@ -59,13 +59,8 @@ constructor(
private val mainExecutor: Executor,
private val callback: Callback,
private val interactionJankMonitor: InteractionJankMonitor,
- private val featureFlags: AnimationFeatureFlags,
private val transitionAnimator: TransitionAnimator =
- TransitionAnimator(
- mainExecutor,
- TIMINGS,
- INTERPOLATORS,
- ),
+ TransitionAnimator(mainExecutor, TIMINGS, INTERPOLATORS),
private val isForTesting: Boolean = false,
) {
private companion object {
@@ -219,7 +214,7 @@ constructor(
dialog: Dialog,
view: View,
cuj: DialogCuj? = null,
- animateBackgroundBoundsChange: Boolean = false
+ animateBackgroundBoundsChange: Boolean = false,
) {
val controller = Controller.fromView(view, cuj)
if (controller == null) {
@@ -245,7 +240,7 @@ constructor(
fun show(
dialog: Dialog,
controller: Controller,
- animateBackgroundBoundsChange: Boolean = false
+ animateBackgroundBoundsChange: Boolean = false,
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException(
@@ -263,15 +258,14 @@ constructor(
val controller =
animatedParent?.dialogContentWithBackground?.let {
Controller.fromView(it, controller.cuj)
- }
- ?: controller
+ } ?: controller
// Make sure we don't run the launch animation from the same source twice at the same time.
if (openedDialogs.any { it.controller.sourceIdentity == controller.sourceIdentity }) {
Log.e(
TAG,
"Not running dialog launch animation from source as it is already expanded into a" +
- " dialog"
+ " dialog",
)
dialog.show()
return
@@ -288,7 +282,6 @@ constructor(
animateBackgroundBoundsChange = animateBackgroundBoundsChange,
parentAnimatedDialog = animatedParent,
forceDisableSynchronization = isForTesting,
- featureFlags = featureFlags,
)
openedDialogs.add(animatedDialog)
@@ -305,7 +298,7 @@ constructor(
dialog: Dialog,
animateFrom: Dialog,
cuj: DialogCuj? = null,
- animateBackgroundBoundsChange: Boolean = false
+ animateBackgroundBoundsChange: Boolean = false,
) {
val view =
openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
@@ -313,7 +306,7 @@ constructor(
Log.w(
TAG,
"Showing dialog $dialog normally as the dialog it is shown from was not shown " +
- "using DialogTransitionAnimator"
+ "using DialogTransitionAnimator",
)
dialog.show()
return
@@ -323,7 +316,7 @@ constructor(
dialog,
view,
animateBackgroundBoundsChange = animateBackgroundBoundsChange,
- cuj = cuj
+ cuj = cuj,
)
}
@@ -346,8 +339,7 @@ constructor(
val animatedDialog =
openedDialogs.firstOrNull {
it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
- }
- ?: return null
+ } ?: return null
return createActivityTransitionController(animatedDialog, cujType)
}
@@ -373,7 +365,7 @@ constructor(
private fun createActivityTransitionController(
animatedDialog: AnimatedDialog,
- cujType: Int? = null
+ cujType: Int? = null,
): ActivityTransitionAnimator.Controller? {
// At this point, we know that the intent of the caller is to dismiss the dialog to show
// an app, so we disable the exit animation into the source because we will never want to
@@ -440,7 +432,7 @@ constructor(
}
private fun disableDialogDismiss() {
- dialog.setDismissOverride { /* Do nothing */}
+ dialog.setDismissOverride { /* Do nothing */ }
}
private fun enableDialogDismiss() {
@@ -530,7 +522,6 @@ private class AnimatedDialog(
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
private val forceDisableSynchronization: Boolean,
- private val featureFlags: AnimationFeatureFlags,
) {
/**
* The DecorView of this dialog window.
@@ -643,8 +634,7 @@ private class AnimatedDialog(
originalDialogBackgroundColor =
GhostedViewTransitionAnimatorController.findGradientDrawable(background)
?.color
- ?.defaultColor
- ?: Color.BLACK
+ ?.defaultColor ?: Color.BLACK
// Make the background view invisible until we start the animation. We use the transition
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
@@ -700,7 +690,7 @@ private class AnimatedDialog(
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
dialogContentWithBackground.removeOnLayoutChangeListener(this)
@@ -717,9 +707,7 @@ private class AnimatedDialog(
// the dialog.
dialog.setDismissOverride(this::onDialogDismissed)
- if (featureFlags.isPredictiveBackQsDialogAnim) {
- dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
- }
+ dialog.registerAnimationOnBackInvoked(targetView = dialogContentWithBackground)
// Show the dialog.
dialog.show()
@@ -815,7 +803,7 @@ private class AnimatedDialog(
if (hasInstrumentedJank) {
interactionJankMonitor.end(controller.cuj!!.cujType)
}
- }
+ },
)
}
@@ -888,14 +876,14 @@ private class AnimatedDialog(
onAnimationFinished(true /* instantDismiss */)
onDialogDismissed(this@AnimatedDialog)
}
- }
+ },
)
}
private fun startAnimation(
isLaunching: Boolean,
onLaunchAnimationStart: () -> Unit = {},
- onLaunchAnimationEnd: () -> Unit = {}
+ onLaunchAnimationEnd: () -> Unit = {},
) {
// Create 2 controllers to animate both the dialog and the source.
val startController =
@@ -969,7 +957,7 @@ private class AnimatedDialog(
override fun onTransitionAnimationProgress(
state: TransitionAnimator.State,
progress: Float,
- linearProgress: Float
+ linearProgress: Float,
) {
startController.onTransitionAnimationProgress(state, progress, linearProgress)
@@ -1026,7 +1014,7 @@ private class AnimatedDialog(
oldLeft: Int,
oldTop: Int,
oldRight: Int,
- oldBottom: Int
+ oldBottom: Int,
) {
// Don't animate if bounds didn't actually change.
if (left == oldLeft && top == oldTop && right == oldRight && bottom == oldBottom) {
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
index e02e8b483543..5f1f588bb2b5 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/NestedDraggable.kt
@@ -37,6 +37,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerId
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.input.pointer.SuspendingPointerInputModifierNode
import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,7 +53,6 @@ import androidx.compose.ui.node.currentValueOf
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.util.fastSumBy
import com.android.compose.modifiers.thenIf
import kotlin.math.sign
import kotlinx.coroutines.CompletableDeferred
@@ -81,7 +81,13 @@ interface NestedDraggable {
* in the direction given by [sign], with the given number of [pointersDown] when the touch slop
* was detected.
*/
- fun onDragStarted(position: Offset, sign: Float, pointersDown: Int): Controller
+ fun onDragStarted(
+ position: Offset,
+ sign: Float,
+ pointersDown: Int,
+ // TODO(b/382665591): Make this non-nullable.
+ pointerType: PointerType?,
+ ): Controller
/**
* Whether this draggable should consume any scroll amount with the given [sign] coming from a
@@ -184,8 +190,8 @@ private class NestedDraggableNode(
*/
private var lastFirstDown: Offset? = null
- /** The number of pointers down. */
- private var pointersDownCount = 0
+ /** The pointers currently down, in order of which they were done and mapping to their type. */
+ private val pointersDown = linkedMapOf<PointerId, PointerType>()
init {
delegate(nestedScrollModifierNode(this, nestedScrollDispatcher))
@@ -256,7 +262,9 @@ private class NestedDraggableNode(
check(down.position == lastFirstDown) {
"Position from detectDrags() is not the same as position in trackDownPosition()"
}
- check(pointersDownCount == 1) { "pointersDownCount is equal to $pointersDownCount" }
+ check(pointersDown.size == 1 && pointersDown.keys.first() == down.id) {
+ "pointersDown should only contain $down but it contains $pointersDown"
+ }
var overSlop = 0f
val onTouchSlopReached = { change: PointerInputChange, over: Float ->
@@ -295,8 +303,9 @@ private class NestedDraggableNode(
}
}
- check(pointersDownCount > 0) { "pointersDownCount is equal to $pointersDownCount" }
- val controller = draggable.onDragStarted(down.position, sign, pointersDownCount)
+ check(pointersDown.size > 0) { "pointersDown is empty" }
+ val controller =
+ draggable.onDragStarted(down.position, sign, pointersDown.size, drag.type)
if (overSlop != 0f) {
onDrag(controller, drag, overSlop, velocityTracker)
}
@@ -450,20 +459,24 @@ private class NestedDraggableNode(
private suspend fun PointerInputScope.trackDownPosition() {
awaitEachGesture {
- val down = awaitFirstDown(requireUnconsumed = false)
- lastFirstDown = down.position
- pointersDownCount = 1
+ try {
+ val down = awaitFirstDown(requireUnconsumed = false)
+ lastFirstDown = down.position
+ pointersDown[down.id] = down.type
- do {
- pointersDownCount +=
- awaitPointerEvent().changes.fastSumBy { change ->
+ do {
+ awaitPointerEvent().changes.forEach { change ->
when {
- change.changedToDownIgnoreConsumed() -> 1
- change.changedToUpIgnoreConsumed() -> -1
- else -> 0
+ change.changedToDownIgnoreConsumed() -> {
+ pointersDown[change.id] = change.type
+ }
+ change.changedToUpIgnoreConsumed() -> pointersDown.remove(change.id)
}
}
- } while (pointersDownCount > 0)
+ } while (pointersDown.size > 0)
+ } finally {
+ pointersDown.clear()
+ }
}
}
@@ -491,12 +504,13 @@ private class NestedDraggableNode(
if (nestedScrollController == null && draggable.shouldConsumeNestedScroll(sign)) {
val startedPosition = checkNotNull(lastFirstDown) { "lastFirstDown is not set" }
- // TODO(b/382665591): Replace this by check(pointersDownCount > 0).
- val pointersDown = pointersDownCount.coerceAtLeast(1)
+ // TODO(b/382665591): Ensure that there is at least one pointer down.
+ val pointersDownCount = pointersDown.size.coerceAtLeast(1)
+ val pointerType = pointersDown.entries.firstOrNull()?.value
nestedScrollController =
NestedScrollController(
overscrollEffect,
- draggable.onDragStarted(startedPosition, sign, pointersDown),
+ draggable.onDragStarted(startedPosition, sign, pointersDownCount, pointerType),
)
}
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
index 9c49090916e3..7f70e97411f4 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/NestedDraggableTest.kt
@@ -33,10 +33,12 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.test.junit4.ComposeContentTestRule
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performMouseInput
import androidx.compose.ui.test.performTouchInput
import androidx.compose.ui.test.swipeDown
import androidx.compose.ui.test.swipeLeft
@@ -653,6 +655,61 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
assertThat(flingIsDone).isTrue()
}
+ @Test
+ fun pointerType() {
+ val draggable = TestDraggable()
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(touchSlop.toOffset())
+ }
+
+ assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Touch)
+ }
+
+ @Test
+ fun pointerType_mouse() {
+ val draggable = TestDraggable()
+ val touchSlop =
+ rule.setContentWithTouchSlop {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation))
+ }
+
+ rule.onRoot().performMouseInput {
+ moveTo(center)
+ press()
+ moveBy(touchSlop.toOffset())
+ release()
+ }
+
+ assertThat(draggable.onDragStartedPointerType).isEqualTo(PointerType.Mouse)
+ }
+
+ @Test
+ fun pointersDown_clearedWhenDisabled() {
+ val draggable = TestDraggable()
+ var enabled by mutableStateOf(true)
+ rule.setContent {
+ Box(Modifier.fillMaxSize().nestedDraggable(draggable, orientation, enabled = enabled))
+ }
+
+ rule.onRoot().performTouchInput { down(center) }
+
+ enabled = false
+ rule.waitForIdle()
+
+ rule.onRoot().performTouchInput { up() }
+
+ enabled = true
+ rule.waitForIdle()
+
+ rule.onRoot().performTouchInput { down(center) }
+ }
+
private fun ComposeContentTestRule.setContentWithTouchSlop(
content: @Composable () -> Unit
): Float {
@@ -688,6 +745,7 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
var onDragStartedPosition = Offset.Zero
var onDragStartedSign = 0f
var onDragStartedPointersDown = 0
+ var onDragStartedPointerType: PointerType? = null
var onDragDelta = 0f
override fun shouldStartDrag(change: PointerInputChange): Boolean = shouldStartDrag
@@ -696,11 +754,13 @@ class NestedDraggableTest(override val orientation: Orientation) : OrientationAw
position: Offset,
sign: Float,
pointersDown: Int,
+ pointerType: PointerType?,
): NestedDraggable.Controller {
onDragStartedCalled = true
onDragStartedPosition = position
onDragStartedSign = sign
onDragStartedPointersDown = pointersDown
+ onDragStartedPointerType = pointerType
onDragDelta = 0f
onDragStarted.invoke(position, sign)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index 1a8c7f8ba24c..0054a4c899ec 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-@file:OptIn(ExperimentalFoundationApi::class)
-
package com.android.systemui.bouncer.ui.composable
import android.app.AlertDialog
@@ -26,7 +24,6 @@ import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.snap
import androidx.compose.animation.core.tween
-import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
@@ -99,7 +96,6 @@ import com.android.compose.animation.scene.transitions
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
index eb62d336b0df..328fec591b41 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt
@@ -16,13 +16,11 @@
package com.android.systemui.bouncer.ui.composable
+import androidx.annotation.VisibleForTesting
import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import com.android.compose.windowsizeclass.LocalWindowSizeClass
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
-import com.android.systemui.bouncer.ui.helper.SizeClass
-import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal
/**
* Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If
@@ -57,3 +55,50 @@ private fun WindowHeightSizeClass.toEnum(): SizeClass {
else -> error("Unsupported WindowHeightSizeClass \"$this\"")
}
}
+
+/** Enumerates all known adaptive layout configurations. */
+enum class BouncerSceneLayout {
+ /** The default UI with the bouncer laid out normally. */
+ STANDARD_BOUNCER,
+ /** The bouncer is displayed vertically stacked with the user switcher. */
+ BELOW_USER_SWITCHER,
+ /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
+ BESIDE_USER_SWITCHER,
+ /** The bouncer is split in two with both sides shown side-by-side. */
+ SPLIT_BOUNCER,
+}
+
+/** Enumerates the supported window size classes. */
+enum class SizeClass {
+ COMPACT,
+ MEDIUM,
+ EXPANDED,
+}
+
+/**
+ * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow
+ * for testing that's not dependent on Compose.
+ */
+@VisibleForTesting
+fun calculateLayoutInternal(
+ width: SizeClass,
+ height: SizeClass,
+ isOneHandedModeSupported: Boolean,
+): BouncerSceneLayout {
+ return when (height) {
+ SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
+ SizeClass.MEDIUM ->
+ when (width) {
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
+ }
+ SizeClass.EXPANDED ->
+ when (width) {
+ SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
+ SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
+ SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
+ }
+ }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isOneHandedModeSupported }
+ ?: BouncerSceneLayout.STANDARD_BOUNCER
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index c3dc84d0a12c..a6a63624cf7c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.scene.ContentKey
import com.android.compose.animation.scene.MutableSceneTransitionLayoutState
@@ -43,11 +44,13 @@ import com.android.compose.animation.scene.SceneTransitions
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.observableTransitionState
+import com.android.systemui.lifecycle.rememberActivated
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.qs.ui.composable.QuickSettingsTheme
import com.android.systemui.ribbon.ui.composable.BottomRightCornerRibbon
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import javax.inject.Provider
@@ -82,16 +85,38 @@ fun SceneContainer(
sceneTransitions: SceneTransitions,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
modifier: Modifier = Modifier,
) {
val coroutineScope = rememberCoroutineScope()
- val state: MutableSceneTransitionLayoutState = remember {
- MutableSceneTransitionLayoutState(
- initialScene = initialSceneKey,
- canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
- transitions = sceneTransitions,
- )
- }
+
+ val view = LocalView.current
+ val sceneJankMonitor =
+ rememberActivated(traceName = "sceneJankMonitor") { sceneJankMonitorFactory.create() }
+
+ val state: MutableSceneTransitionLayoutState =
+ remember(view, sceneJankMonitor) {
+ MutableSceneTransitionLayoutState(
+ initialScene = initialSceneKey,
+ canChangeScene = { toScene -> viewModel.canChangeScene(toScene) },
+ transitions = sceneTransitions,
+ onTransitionStart = { transition ->
+ sceneJankMonitor.onTransitionStart(
+ view = view,
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ onTransitionEnd = { transition ->
+ sceneJankMonitor.onTransitionEnd(
+ from = transition.fromContent,
+ to = transition.toContent,
+ cuj = transition.cuj,
+ )
+ },
+ )
+ }
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index ee8535eff3ae..6d24fc16df23 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -3,6 +3,7 @@ package com.android.systemui.scene.ui.composable
import androidx.compose.animation.core.spring
import com.android.compose.animation.scene.TransitionKey
import com.android.compose.animation.scene.transitions
+import com.android.internal.jank.Cuj
import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
@@ -56,14 +57,41 @@ val SceneContainerTransitions = transitions {
from(Scenes.Dream, to = Scenes.Bouncer) { dreamToBouncerTransition() }
from(Scenes.Dream, to = Scenes.Communal) { dreamToCommunalTransition() }
from(Scenes.Dream, to = Scenes.Gone) { dreamToGoneTransition() }
- from(Scenes.Dream, to = Scenes.Shade) { dreamToShadeTransition() }
- from(Scenes.Gone, to = Scenes.Shade) { goneToShadeTransition() }
- from(Scenes.Gone, to = Scenes.Shade, key = ToSplitShade) { goneToSplitShadeTransition() }
- from(Scenes.Gone, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
+ from(Scenes.Dream, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
+ dreamToShadeTransition()
+ }
+ from(Scenes.Gone, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
+ goneToShadeTransition()
+ }
+ from(
+ Scenes.Gone,
+ to = Scenes.Shade,
+ key = ToSplitShade,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
+ goneToSplitShadeTransition()
+ }
+ from(
+ Scenes.Gone,
+ to = Scenes.Shade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
goneToShadeTransition(durationScale = 0.9)
}
- from(Scenes.Gone, to = Scenes.QuickSettings) { goneToQuickSettingsTransition() }
- from(Scenes.Gone, to = Scenes.QuickSettings, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Gone,
+ to = Scenes.QuickSettings,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
+ goneToQuickSettingsTransition()
+ }
+ from(
+ Scenes.Gone,
+ to = Scenes.QuickSettings,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
goneToQuickSettingsTransition(durationScale = 0.9)
}
@@ -78,49 +106,112 @@ val SceneContainerTransitions = transitions {
}
from(Scenes.Lockscreen, to = Scenes.Communal) { lockscreenToCommunalTransition() }
from(Scenes.Lockscreen, to = Scenes.Dream) { lockscreenToDreamTransition() }
- from(Scenes.Lockscreen, to = Scenes.Shade) { lockscreenToShadeTransition() }
- from(Scenes.Lockscreen, to = Scenes.Shade, key = ToSplitShade) {
+ from(Scenes.Lockscreen, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
+ lockscreenToShadeTransition()
+ }
+ from(
+ Scenes.Lockscreen,
+ to = Scenes.Shade,
+ key = ToSplitShade,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
lockscreenToSplitShadeTransition()
sharedElement(Shade.Elements.BackgroundScrim, enabled = false)
}
- from(Scenes.Lockscreen, to = Scenes.Shade, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Lockscreen,
+ to = Scenes.Shade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
lockscreenToShadeTransition(durationScale = 0.9)
}
- from(Scenes.Lockscreen, to = Scenes.QuickSettings) { lockscreenToQuickSettingsTransition() }
+ from(
+ Scenes.Lockscreen,
+ to = Scenes.QuickSettings,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
+ lockscreenToQuickSettingsTransition()
+ }
from(Scenes.Lockscreen, to = Scenes.Gone) { lockscreenToGoneTransition() }
- from(Scenes.QuickSettings, to = Scenes.Shade) {
+ from(
+ Scenes.QuickSettings,
+ to = Scenes.Shade,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
reversed { shadeToQuickSettingsTransition() }
sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
}
- from(Scenes.Shade, to = Scenes.QuickSettings) { shadeToQuickSettingsTransition() }
- from(Scenes.Shade, to = Scenes.Lockscreen) {
+ from(
+ Scenes.Shade,
+ to = Scenes.QuickSettings,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
+ shadeToQuickSettingsTransition()
+ }
+ from(Scenes.Shade, to = Scenes.Lockscreen, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
reversed { lockscreenToShadeTransition() }
sharedElement(Notifications.Elements.NotificationStackPlaceholder, enabled = false)
sharedElement(Notifications.Elements.HeadsUpNotificationPlaceholder, enabled = false)
}
- from(Scenes.Shade, to = Scenes.Lockscreen, key = ToSplitShade) {
+ from(
+ Scenes.Shade,
+ to = Scenes.Lockscreen,
+ key = ToSplitShade,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
reversed { lockscreenToSplitShadeTransition() }
}
- from(Scenes.Communal, to = Scenes.Shade) { communalToShadeTransition() }
+ from(Scenes.Communal, to = Scenes.Shade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
+ communalToShadeTransition()
+ }
from(Scenes.Communal, to = Scenes.Bouncer) { communalToBouncerTransition() }
// Overlay transitions
- to(Overlays.NotificationsShade) { toNotificationsShadeTransition() }
- to(Overlays.QuickSettingsShade) { toQuickSettingsShadeTransition() }
- from(Overlays.NotificationsShade, to = Overlays.QuickSettingsShade) {
+ to(Overlays.NotificationsShade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE) {
+ toNotificationsShadeTransition()
+ }
+ to(Overlays.QuickSettingsShade, cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE) {
+ toQuickSettingsShadeTransition()
+ }
+ from(
+ Overlays.NotificationsShade,
+ to = Overlays.QuickSettingsShade,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
notificationsShadeToQuickSettingsShadeTransition()
}
- from(Scenes.Gone, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Gone,
+ to = Overlays.NotificationsShade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
toNotificationsShadeTransition(durationScale = 0.9)
}
- from(Scenes.Gone, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Gone,
+ to = Overlays.QuickSettingsShade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
toQuickSettingsShadeTransition(durationScale = 0.9)
}
- from(Scenes.Lockscreen, to = Overlays.NotificationsShade, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Lockscreen,
+ to = Overlays.NotificationsShade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE,
+ ) {
toNotificationsShadeTransition(durationScale = 0.9)
}
- from(Scenes.Lockscreen, to = Overlays.QuickSettingsShade, key = SlightlyFasterShadeCollapse) {
+ from(
+ Scenes.Lockscreen,
+ to = Overlays.QuickSettingsShade,
+ key = SlightlyFasterShadeCollapse,
+ cuj = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE,
+ ) {
toQuickSettingsShadeTransition(durationScale = 0.9)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index c704a3e96467..de428a7d3548 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
+import com.android.compose.gesture.NestedScrollableBound
import com.android.compose.gesture.effect.ContentOverscrollEffect
/**
@@ -238,6 +239,18 @@ interface BaseContentScope : ElementStateScope {
fun Modifier.noResizeDuringTransitions(): Modifier
/**
+ * Temporarily disable this content swipe actions when any scrollable below this modifier has
+ * consumed any amount of scroll delta, until the scroll gesture is finished.
+ *
+ * This can for instance be used to ensure that a scrollable list is overscrolled once it
+ * reached its bounds instead of directly starting a scene transition from the same scroll
+ * gesture.
+ */
+ fun Modifier.disableSwipesWhenScrolling(
+ bounds: NestedScrollableBound = NestedScrollableBound.Any
+ ): Modifier
+
+ /**
* A [NestedSceneTransitionLayout] will share its elements with its ancestor STLs therefore
* enabling sharedElement transitions between them.
*/
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
index 607e4fadc256..ba92f9bea07d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt
@@ -315,16 +315,10 @@ internal class SwipeAnimation<T : ContentKey>(
val skipAnimation =
hasReachedTargetContent && !contentTransition.isWithinProgressRange(initialProgress)
- val targetOffset =
- if (targetContent == fromContent) {
- 0f
- } else {
- val distance = distance()
- check(distance != DistanceUnspecified) {
- "distance is equal to $DistanceUnspecified"
- }
- distance
- }
+ val distance = distance()
+ check(distance != DistanceUnspecified) { "distance is equal to $DistanceUnspecified" }
+
+ val targetOffset = if (targetContent == fromContent) 0f else distance
// If the effective current content changed, it should be reflected right now in the
// current state, even before the settle animation is ongoing. That way all the
@@ -343,7 +337,16 @@ internal class SwipeAnimation<T : ContentKey>(
}
val animatable =
- Animatable(initialOffset, OffsetVisibilityThreshold).also { offsetAnimation = it }
+ Animatable(initialOffset, OffsetVisibilityThreshold).also {
+ offsetAnimation = it
+
+ // We should animate when the progress value is between [0, 1].
+ if (distance > 0) {
+ it.updateBounds(0f, distance)
+ } else {
+ it.updateBounds(distance, 0f)
+ }
+ }
check(isAnimatingOffset())
@@ -370,42 +373,26 @@ internal class SwipeAnimation<T : ContentKey>(
val velocityConsumed = CompletableDeferred<Float>()
offsetAnimationRunnable.complete {
- try {
+ val result =
animatable.animateTo(
targetValue = targetOffset,
animationSpec = swipeSpec,
initialVelocity = initialVelocity,
- ) {
- // Immediately stop this transition if we are bouncing on a content that
- // does not bounce.
- if (!contentTransition.isWithinProgressRange(progress)) {
- // We are no longer able to consume the velocity, the rest can be
- // consumed by another component in the hierarchy.
- velocityConsumed.complete(initialVelocity - velocity)
- throw SnapException()
- }
- }
- } catch (_: SnapException) {
- /* Ignore. */
- } finally {
- if (!velocityConsumed.isCompleted) {
- // The animation consumed the whole available velocity
- velocityConsumed.complete(initialVelocity)
- }
+ )
- // Wait for overscroll to finish so that the transition is removed from the STLState
- // only after the overscroll is done, to avoid dropping frame right when the user
- // lifts their finger and overscroll is animated to 0.
- overscrollCompletable?.await()
- }
+ // We are no longer able to consume the velocity, the rest can be consumed by another
+ // component in the hierarchy.
+ velocityConsumed.complete(initialVelocity - result.endState.velocity)
+
+ // Wait for overscroll to finish so that the transition is removed from the STLState
+ // only after the overscroll is done, to avoid dropping frame right when the user
+ // lifts their finger and overscroll is animated to 0.
+ overscrollCompletable?.await()
}
return velocityConsumed.await()
}
- /** An exception thrown during the animation to stop it immediately. */
- private class SnapException : Exception()
-
private fun canChangeContent(targetContent: ContentKey): Boolean {
return when (val transition = contentTransition) {
is TransitionState.Transition.ChangeScene ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index c5b3df222855..3f6bce724b1b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -54,7 +54,7 @@ private fun DraggableHandlerImpl.contentForSwipes(): Content {
/** Whether swipe should be enabled in the given [orientation]. */
internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
- if (userActions.isEmpty()) {
+ if (userActions.isEmpty() || !areSwipesAllowed()) {
return false
}
@@ -69,6 +69,10 @@ internal fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
* @return The best matching [UserActionResult], or `null` if no match is found.
*/
internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+ if (!areSwipesAllowed()) {
+ return null
+ }
+
var bestPoints = Int.MIN_VALUE
var bestMatch: UserActionResult? = null
userActions.forEach { (actionSwipe, actionResult) ->
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
index 4c15f7a4534f..59b4a09385f5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/content/Content.kt
@@ -56,7 +56,10 @@ import com.android.compose.animation.scene.effect.GestureEffect
import com.android.compose.animation.scene.effect.VisualEffect
import com.android.compose.animation.scene.element
import com.android.compose.animation.scene.modifiers.noResizeDuringTransitions
+import com.android.compose.gesture.NestedScrollControlState
+import com.android.compose.gesture.NestedScrollableBound
import com.android.compose.gesture.effect.OffsetOverscrollEffect
+import com.android.compose.gesture.nestedScrollController
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.ContainerState
import com.android.compose.ui.graphics.container
@@ -70,7 +73,8 @@ internal sealed class Content(
actions: Map<UserAction.Resolved, UserActionResult>,
zIndex: Float,
) {
- internal val scope = ContentScopeImpl(layoutImpl, content = this)
+ private val nestedScrollControlState = NestedScrollControlState()
+ internal val scope = ContentScopeImpl(layoutImpl, content = this, nestedScrollControlState)
val containerState = ContainerState()
var content by mutableStateOf(content)
@@ -101,11 +105,14 @@ internal sealed class Content(
scope.content()
}
}
+
+ fun areSwipesAllowed(): Boolean = nestedScrollControlState.isOuterScrollAllowed
}
internal class ContentScopeImpl(
private val layoutImpl: SceneTransitionLayoutImpl,
private val content: Content,
+ private val nestedScrollControlState: NestedScrollControlState,
) : ContentScope, ElementStateScope by layoutImpl.elementStateScope {
override val contentKey: ContentKey
get() = content.key
@@ -176,6 +183,10 @@ internal class ContentScopeImpl(
return noResizeDuringTransitions(layoutState = layoutImpl.state)
}
+ override fun Modifier.disableSwipesWhenScrolling(bounds: NestedScrollableBound): Modifier {
+ return nestedScrollController(nestedScrollControlState, bounds)
+ }
+
@Composable
override fun NestedSceneTransitionLayout(
state: SceneTransitionLayoutState,
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
new file mode 100644
index 000000000000..06a9735d97e2
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ContentTest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.rememberScrollableState
+import androidx.compose.foundation.gestures.scrollable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compose.animation.scene.TestScenes.SceneA
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ContentTest {
+ @get:Rule val rule = createComposeRule()
+
+ @Test
+ fun disableSwipesWhenScrolling() {
+ lateinit var layoutImpl: SceneTransitionLayoutImpl
+ rule.setContent {
+ SceneTransitionLayoutForTesting(
+ remember { MutableSceneTransitionLayoutState(SceneA) },
+ onLayoutImpl = { layoutImpl = it },
+ ) {
+ scene(SceneA) {
+ Box(
+ Modifier.fillMaxSize()
+ .disableSwipesWhenScrolling()
+ .scrollable(rememberScrollableState { it }, Orientation.Vertical)
+ )
+ }
+ }
+ }
+
+ val content = layoutImpl.content(SceneA)
+ assertThat(content.areSwipesAllowed()).isTrue()
+ rule.onRoot().performTouchInput {
+ down(topLeft)
+ moveBy(bottomLeft)
+ }
+
+ assertThat(content.areSwipesAllowed()).isFalse()
+ rule.onRoot().performTouchInput { up() }
+ assertThat(content.areSwipesAllowed()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index 7c8c6e5f6c12..e580e3c40690 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -21,6 +21,7 @@ import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.size
@@ -33,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -43,6 +45,9 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onChild
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.IntOffset
@@ -469,4 +474,41 @@ class SceneTransitionLayoutTest {
assertThat(layoutImpl.overlaysOrNullForTest()).isNull()
}
+
+ @Test
+ fun transitionProgressBoundedBetween0And1() {
+ val layoutWidth = 200.dp
+ val layoutHeight = 400.dp
+
+ // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
+ // detected as a drag event.
+ var touchSlop = 0f
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(initialScene = SceneA) }
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ SceneTransitionLayout(state, Modifier.size(layoutWidth, layoutHeight)) {
+ scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) {
+ Spacer(Modifier.fillMaxSize())
+ }
+ scene(SceneB) { Spacer(Modifier.fillMaxSize()) }
+ }
+ }
+ assertThat(state.transitionState).isIdle()
+
+ rule.mainClock.autoAdvance = false
+
+ // Swipe the verticalSwipeDistance.
+ rule.onRoot().performTouchInput {
+ swipeDown(endY = bottom + touchSlop, durationMillis = 50)
+ }
+
+ rule.mainClock.advanceTimeBy(16)
+ val transition = assertThat(state.transitionState).isSceneTransition()
+ assertThat(transition).isNotNull()
+ assertThat(transition).hasProgress(1f, tolerance = 0.01f)
+
+ rule.mainClock.advanceTimeBy(16)
+ // Fling animation, we are overscrolling now. Progress should always be between [0, 1].
+ assertThat(transition).hasProgress(1f)
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
index 0f8ca947479b..2b0825f39243 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt
@@ -30,7 +30,6 @@ import android.util.AttributeSet
import android.util.Log
import android.util.MathUtils
import android.util.TypedValue
-import android.view.View.MeasureSpec.AT_MOST
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
import android.widget.TextView
@@ -77,7 +76,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
var maxSingleDigitWidth = -1
var digitTranslateAnimator: DigitTranslateAnimator? = null
var aodFontSizePx: Float = -1F
- var isVertical: Boolean = false
// Store the font size when there's no height constraint as a reference when adjusting font size
private var lastUnconstrainedTextSize: Float = Float.MAX_VALUE
@@ -148,16 +146,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.d("onMeasure()")
- if (isVertical) {
- // use at_most to avoid apply measuredWidth from last measuring to measuredHeight
- // cause we use max to setMeasuredDimension
- super.onMeasure(
- MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), AT_MOST),
- MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), AT_MOST),
- )
- } else {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec)
- }
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val layout = this.layout
if (layout != null) {
@@ -213,18 +202,10 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
)
}
- if (isVertical) {
- expectedWidth = expectedHeight.also { expectedHeight = expectedWidth }
- }
setMeasuredDimension(expectedWidth, expectedHeight)
}
override fun onDraw(canvas: Canvas) {
- if (isVertical) {
- canvas.save()
- canvas.translate(0F, measuredHeight.toFloat())
- canvas.rotate(-90F)
- }
logger.d({ "onDraw(); ls: $str1" }) { str1 = textAnimator.textInterpolator.shapedText }
val translation = getLocalTranslation()
canvas.translate(translation.x.toFloat(), translation.y.toFloat())
@@ -238,9 +219,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
canvas.translate(-it.updatedTranslate.x.toFloat(), -it.updatedTranslate.y.toFloat())
}
canvas.translate(-translation.x.toFloat(), -translation.y.toFloat())
- if (isVertical) {
- canvas.restore()
- }
}
override fun invalidate() {
@@ -353,18 +331,20 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
}
private fun updateXtranslation(inPoint: Point, interpolatedTextBounds: Rect): Point {
- val viewWidth = if (isVertical) measuredHeight else measuredWidth
when (horizontalAlignment) {
HorizontalAlignment.LEFT -> {
inPoint.x = lockScreenPaint.strokeWidth.toInt() - interpolatedTextBounds.left
}
HorizontalAlignment.RIGHT -> {
inPoint.x =
- viewWidth - interpolatedTextBounds.right - lockScreenPaint.strokeWidth.toInt()
+ measuredWidth -
+ interpolatedTextBounds.right -
+ lockScreenPaint.strokeWidth.toInt()
}
HorizontalAlignment.CENTER -> {
inPoint.x =
- (viewWidth - interpolatedTextBounds.width()) / 2 - interpolatedTextBounds.left
+ (measuredWidth - interpolatedTextBounds.width()) / 2 -
+ interpolatedTextBounds.left
}
}
return inPoint
@@ -373,7 +353,6 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
// translation of reference point of text
// used for translation when calling textInterpolator
private fun getLocalTranslation(): Point {
- val viewHeight = if (isVertical) measuredWidth else measuredHeight
val interpolatedTextBounds = updateInterpolatedTextBounds()
val localTranslation = Point(0, 0)
val correctedBaseline = if (baseline != -1) baseline else baselineFromMeasure
@@ -381,7 +360,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
when (verticalAlignment) {
VerticalAlignment.CENTER -> {
localTranslation.y =
- ((viewHeight - interpolatedTextBounds.height()) / 2 -
+ ((measuredHeight - interpolatedTextBounds.height()) / 2 -
interpolatedTextBounds.top -
correctedBaseline)
}
@@ -392,7 +371,7 @@ open class SimpleDigitalClockTextView(clockCtx: ClockContext, attrs: AttributeSe
}
VerticalAlignment.BOTTOM -> {
localTranslation.y =
- viewHeight -
+ measuredHeight -
interpolatedTextBounds.bottom -
lockScreenPaint.strokeWidth.toInt() -
correctedBaseline
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 2c1dacdfae73..4d2a6d9bd57a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -232,7 +232,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsLessThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
- `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(3)
@@ -249,7 +248,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun testOnViewAttached_withAutoPinConfirmationFailedPasswordAttemptsMoreThan5() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
- `when`(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
`when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
`when`(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
`when`(lockPatternUtils.getCurrentFailedPasswordAttempts(anyInt())).thenReturn(6)
@@ -275,7 +273,6 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
@Test
fun onUserInput_autoConfirmation_attemptsUnlock() {
val pinViewController = constructPinViewController(mockKeyguardPinView)
- whenever(featureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)).thenReturn(true)
whenever(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
whenever(lockPatternUtils.isAutoPinConfirmEnabled(anyInt())).thenReturn(true)
whenever(passwordTextView.text).thenReturn("000000")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
index b33a83cf202a..a65415509d38 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerPredictiveBackTest.kt
@@ -69,6 +69,7 @@ import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Scene
import com.android.systemui.scene.ui.composable.SceneContainer
import com.android.systemui.scene.ui.composable.SceneContainerTransitions
+import com.android.systemui.scene.ui.view.sceneJankMonitorFactory
import com.android.systemui.testKosmos
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.awaitCancellation
@@ -193,6 +194,7 @@ class BouncerPredictiveBackTest : SysuiTestCase() {
overlayByKey = emptyMap(),
dataSourceDelegator = kosmos.sceneDataSourceDelegator,
qsSceneAdapter = { kosmos.fakeQsSceneAdapter },
+ sceneJankMonitorFactory = kosmos.sceneJankMonitorFactory,
)
}
},
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayoutTest.kt
index 3ede841bb25b..b4b41787d833 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayoutTest.kt
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.bouncer.ui.helper
+package com.android.systemui.bouncer.ui.composable
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER
+import com.android.systemui.bouncer.ui.composable.BouncerSceneLayout.BELOW_USER_SWITCHER
+import com.android.systemui.bouncer.ui.composable.BouncerSceneLayout.BESIDE_USER_SWITCHER
+import com.android.systemui.bouncer.ui.composable.BouncerSceneLayout.SPLIT_BOUNCER
+import com.android.systemui.bouncer.ui.composable.BouncerSceneLayout.STANDARD_BOUNCER
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import org.junit.Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
index e659ef274980..698fac107a1d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepositoryTest.kt
@@ -18,7 +18,9 @@ package com.android.systemui.keyboard.shortcut.data.repository
import android.content.Context
import android.content.Context.INPUT_SERVICE
+import android.content.Intent
import android.hardware.input.InputGestureData
+import android.hardware.input.InputManager
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.fakeInputManager
import android.platform.test.annotations.EnableFlags
@@ -27,9 +29,12 @@ import androidx.test.filters.SmallTest
import com.android.hardware.input.Flags.FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES
import com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER
import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyboard.shortcut.customInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.allAppsInputGestureData
+import com.android.systemui.keyboard.shortcut.data.source.TestShortcuts.goHomeInputGestureData
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
import com.android.systemui.kosmos.testScope
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.settings.userTracker
@@ -48,18 +53,41 @@ import org.mockito.kotlin.whenever
@EnableFlags(FLAG_ENABLE_CUSTOMIZABLE_INPUT_GESTURES, FLAG_USE_KEY_GESTURE_EVENT_HANDLER)
class CustomInputGesturesRepositoryTest : SysuiTestCase() {
- private val mockUserContext: Context = mock()
+ private val primaryUserContext: Context = mock()
+ private val secondaryUserContext: Context = mock()
+ private var activeUserContext: Context = primaryUserContext
+
private val kosmos = testKosmos().also {
- it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockUserContext })
+ it.userTracker = FakeUserTracker(onCreateCurrentUserContext = { activeUserContext })
}
private val inputManager = kosmos.fakeInputManager.inputManager
+ private val broadcastDispatcher = kosmos.broadcastDispatcher
+ private val inputManagerForSecondaryUser: InputManager = mock()
private val testScope = kosmos.testScope
+ private val testHelper = kosmos.shortcutHelperTestHelper
private val customInputGesturesRepository = kosmos.customInputGesturesRepository
@Before
- fun setup(){
- whenever(mockUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+ fun setup() {
+ activeUserContext = primaryUserContext
+ whenever(primaryUserContext.getSystemService(INPUT_SERVICE)).thenReturn(inputManager)
+ whenever(secondaryUserContext.getSystemService(INPUT_SERVICE))
+ .thenReturn(inputManagerForSecondaryUser)
+ }
+
+ @Test
+ fun customInputGestures_emitsNewUsersInputGesturesWhenUserIsSwitch() {
+ testScope.runTest {
+ setCustomInputGesturesForPrimaryUser(allAppsInputGestureData)
+ setCustomInputGesturesForSecondaryUser(goHomeInputGestureData)
+
+ val inputGestures by collectLastValue(customInputGesturesRepository.customInputGestures)
+ assertThat(inputGestures).containsExactly(allAppsInputGestureData)
+
+ switchToSecondaryUser()
+ assertThat(inputGestures).containsExactly(goHomeInputGestureData)
+ }
}
@Test
@@ -115,4 +143,24 @@ class CustomInputGesturesRepositoryTest : SysuiTestCase() {
}
}
+ private fun setCustomInputGesturesForPrimaryUser(vararg inputGesture: InputGestureData) {
+ whenever(
+ inputManager.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).thenReturn(inputGesture.toList())
+ }
+
+ private fun setCustomInputGesturesForSecondaryUser(vararg inputGesture: InputGestureData) {
+ whenever(
+ inputManagerForSecondaryUser.getCustomInputGestures(/* filter= */ InputGestureData.Filter.KEY)
+ ).thenReturn(inputGesture.toList())
+ }
+
+ private fun switchToSecondaryUser() {
+ activeUserContext = secondaryUserContext
+ broadcastDispatcher.sendIntentToMatchingReceiversOnly(
+ context,
+ Intent(Intent.ACTION_USER_SWITCHED)
+ )
+ }
+
} \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
index e60d971c7289..282bebcd629a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractorTest.kt
@@ -25,13 +25,14 @@ import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
@@ -75,25 +76,16 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
}
@Test
- @DisableSceneContainer
- fun clockShouldBeCentered_sceneContainerFlagOff_basedOnRepository() =
- testScope.runTest {
- val value by collectLastValue(underTest.clockShouldBeCentered)
- kosmos.keyguardInteractor.setClockShouldBeCentered(true)
- assertThat(value).isTrue()
-
- kosmos.keyguardInteractor.setClockShouldBeCentered(false)
- assertThat(value).isFalse()
- }
-
- @Test
@EnableSceneContainer
fun clockSize_forceSmallClock_SMALL() =
testScope.runTest {
val value by collectLastValue(underTest.clockSize)
kosmos.fakeKeyguardClockRepository.setShouldForceSmallClock(true)
kosmos.fakeFeatureFlagsClassic.set(Flags.LOCKSCREEN_ENABLE_LANDSCAPE, true)
- transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
assertThat(value).isEqualTo(ClockSize.SMALL)
}
@@ -190,7 +182,10 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
val value by collectLastValue(underTest.clockShouldBeCentered)
kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
- transitionTo(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ )
assertThat(value).isTrue()
}
@@ -201,15 +196,187 @@ class KeyguardClockInteractorTest : SysuiTestCase() {
val value by collectLastValue(underTest.clockShouldBeCentered)
kosmos.shadeRepository.setShadeLayoutWide(true)
kosmos.activeNotificationListRepository.setActiveNotifs(1)
- transitionTo(KeyguardState.AOD, KeyguardState.LOCKSCREEN)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
assertThat(value).isFalse()
}
- private suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
- with(kosmos.fakeKeyguardTransitionRepository) {
- sendTransitionStep(TransitionStep(from, to, 0f, TransitionState.STARTED))
- sendTransitionStep(TransitionStep(from, to, 0.5f, TransitionState.RUNNING))
- sendTransitionStep(TransitionStep(from, to, 1f, TransitionState.FINISHED))
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_notSplitMode_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(false)
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_lockscreen_withNotifs_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
+ assertThat(value).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_lockscreen_withoutNotifs_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_LsToAod_withNotifs_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ )
+ assertThat(value).isFalse()
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ )
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_AodToLs_withNotifs_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ )
+ assertThat(value).isTrue()
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
+ assertThat(value).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_Aod_withPulsingNotifs_false() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.AOD,
+ )
+ assertThat(value).isTrue()
+ kosmos.fakeKeyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(
+ from = DozeStateModel.DOZE_AOD,
+ to = DozeStateModel.DOZE_PULSING,
+ )
+ )
+ assertThat(value).isFalse()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_LStoGone_withoutNotifs_true() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ )
+ assertThat(value).isTrue()
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_AodOn_GoneToAOD() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ assertThat(value).isTrue()
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ assertThat(value).isTrue()
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.GONE,
+ KeyguardState.AOD,
+ )
+ assertThat(value).isTrue()
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun clockShouldBeCentered_sceneContainerFlagOff_splitMode_AodOff_GoneToDoze() =
+ testScope.runTest {
+ val value by collectLastValue(underTest.clockShouldBeCentered)
+ kosmos.shadeRepository.setShadeLayoutWide(true)
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.DOZING,
+ KeyguardState.LOCKSCREEN,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ assertThat(value).isTrue()
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.LOCKSCREEN,
+ KeyguardState.GONE,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ assertThat(value).isTrue()
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.GONE,
+ KeyguardState.DOZING,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(1)
+ assertThat(value).isTrue()
+
+ kosmos.fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.DOZING,
+ KeyguardState.LOCKSCREEN,
+ )
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ assertThat(value).isTrue()
}
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 87ab3c89a671..1cf45f8f8b8e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -27,7 +27,6 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.customization.R as customR
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardSmartspaceInteractor
@@ -156,7 +155,6 @@ class ClockSectionTest : SysuiTestCase() {
shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
- fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
fakeConfigurationController.notifyConfigurationChanged()
@@ -181,7 +179,6 @@ class ClockSectionTest : SysuiTestCase() {
shadeRepository.setShadeLayoutWide(true)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
- fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
fakeConfigurationController.notifyConfigurationChanged()
@@ -206,7 +203,6 @@ class ClockSectionTest : SysuiTestCase() {
shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.LARGE)
- fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
fakeConfigurationController.notifyConfigurationChanged()
@@ -230,7 +226,6 @@ class ClockSectionTest : SysuiTestCase() {
shadeRepository.setShadeLayoutWide(true)
keyguardClockInteractor.setClockSize(ClockSize.SMALL)
- fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
fakeConfigurationController.notifyConfigurationChanged()
@@ -254,7 +249,6 @@ class ClockSectionTest : SysuiTestCase() {
shadeRepository.setShadeLayoutWide(false)
keyguardClockInteractor.setClockSize(ClockSize.SMALL)
- fakeKeyguardRepository.setClockShouldBeCentered(true)
notificationsKeyguardInteractor.setNotificationsFullyHidden(true)
keyguardSmartspaceInteractor.setBcSmartspaceVisibility(VISIBLE)
fakeConfigurationController.notifyConfigurationChanged()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index 05a6b8785daf..8a599a1bd948 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -20,15 +20,15 @@ import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.flags.BrokenWithSceneContainer
import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardClockRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.keyguardClockRepository
-import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel.ClockLayout
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.clocks.ClockConfig
@@ -37,6 +37,8 @@ import com.android.systemui.plugins.clocks.ClockFaceConfig
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.res.R
import com.android.systemui.shade.data.repository.shadeRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.ui.fakeSystemBarUtilsProxy
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
@@ -87,7 +89,11 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
- keyguardRepository.setClockShouldBeCentered(true)
+ kosmos.activeNotificationListRepository.setActiveNotifs(0)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
keyguardClockRepository.setClockSize(ClockSize.LARGE)
}
@@ -95,14 +101,18 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @BrokenWithSceneContainer(339465026)
+ @EnableSceneContainer
fun currentClockLayout_splitShadeOn_clockNotCentered_largeClock_splitShadeLargeClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
- keyguardRepository.setClockShouldBeCentered(false)
+ activeNotificationListRepository.setActiveNotifs(1)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
keyguardClockRepository.setClockSize(ClockSize.LARGE)
}
@@ -110,42 +120,46 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @BrokenWithSceneContainer(339465026)
- fun currentClockLayout_splitShadeOn_clockNotCentered_smallClock_splitShadeSmallClock() =
+ @EnableSceneContainer
+ fun currentClockLayout_splitShadeOn_clockNotCentered_forceSmallClock_splitShadeSmallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
shadeRepository.setShadeLayoutWide(true)
- keyguardRepository.setClockShouldBeCentered(false)
- keyguardClockRepository.setClockSize(ClockSize.SMALL)
+ activeNotificationListRepository.setActiveNotifs(1)
+ fakeKeyguardTransitionRepository.transitionTo(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ )
+ fakeKeyguardClockRepository.setShouldForceSmallClock(true)
}
assertThat(currentClockLayout).isEqualTo(ClockLayout.SPLIT_SHADE_SMALL_CLOCK)
}
@Test
- @BrokenWithSceneContainer(339465026)
- fun currentClockLayout_singleShade_smallClock_smallClock() =
+ @EnableSceneContainer
+ fun currentClockLayout_singleShade_withNotifs_smallClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
- keyguardClockRepository.setClockSize(ClockSize.SMALL)
+ activeNotificationListRepository.setActiveNotifs(1)
}
assertThat(currentClockLayout).isEqualTo(ClockLayout.SMALL_CLOCK)
}
@Test
- fun currentClockLayout_singleShade_largeClock_largeClock() =
+ fun currentClockLayout_singleShade_withoutNotifs_largeClock() =
testScope.runTest {
val currentClockLayout by collectLastValue(underTest.currentClockLayout)
with(kosmos) {
shadeRepository.setShadeLayoutWide(false)
- keyguardClockRepository.setClockSize(ClockSize.LARGE)
+ activeNotificationListRepository.setActiveNotifs(0)
}
assertThat(currentClockLayout).isEqualTo(ClockLayout.LARGE_CLOCK)
@@ -195,7 +209,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @BrokenWithSceneContainer(339465026)
+ @DisableSceneContainer
fun testClockSize_dynamicClockSize() =
testScope.runTest {
with(kosmos) {
@@ -219,7 +233,7 @@ class KeyguardClockViewModelTest(flags: FlagsParameterization) : SysuiTestCase()
}
@Test
- @BrokenWithSceneContainer(339465026)
+ @DisableSceneContainer
fun isLargeClockVisible_whenSmallClockSize_isFalse() =
testScope.runTest {
val value by collectLastValue(underTest.isLargeClockVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
index 5798e0776c4f..338b06824e85 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/util/KeyguardTransitionRunner.kt
@@ -24,8 +24,9 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import java.util.function.Consumer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
@@ -36,12 +37,10 @@ import org.junit.Assert.fail
* Gives direct control over ValueAnimator, in order to make transition tests deterministic. See
* [AnimationHandler]. Animators are required to be run on the main thread, so dispatch accordingly.
*/
-class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
- AnimationFrameCallbackProvider {
-
- private var frameCount = 1L
- private var frames = MutableStateFlow(Pair<Long, FrameCallback?>(0L, null))
- private var job: Job? = null
+class KeyguardTransitionRunner(
+ val frames: Flow<Long>,
+ val repository: KeyguardTransitionRepository,
+) {
@Volatile private var isTerminated = false
/**
@@ -54,21 +53,12 @@ class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
maxFrames: Int = 100,
frameCallback: Consumer<Long>? = null,
) {
- // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from main
- // thread
- withContext(Dispatchers.Main) {
- info.animator!!.getAnimationHandler().setProvider(this@KeyguardTransitionRunner)
- }
-
- job =
+ val job =
scope.launch {
- frames.collect {
- val (frameNumber, callback) = it
-
+ frames.collect { frameNumber ->
isTerminated = frameNumber >= maxFrames
if (!isTerminated) {
try {
- withContext(Dispatchers.Main) { callback?.doFrame(frameNumber) }
frameCallback?.accept(frameNumber)
} catch (e: IllegalStateException) {
e.printStackTrace()
@@ -78,27 +68,46 @@ class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
}
withContext(Dispatchers.Main) { repository.startTransition(info) }
- waitUntilComplete(info.animator!!)
+ waitUntilComplete(info, info.animator!!)
+ job.cancel()
}
- private suspend fun waitUntilComplete(animator: ValueAnimator) {
+ private suspend fun waitUntilComplete(info: TransitionInfo, animator: ValueAnimator) {
withContext(Dispatchers.Main) {
val startTime = System.currentTimeMillis()
while (!isTerminated && animator.isRunning()) {
delay(1)
if (System.currentTimeMillis() - startTime > MAX_TEST_DURATION) {
- fail("Failed test due to excessive runtime of: $MAX_TEST_DURATION")
+ fail("Failed due to excessive runtime of: $MAX_TEST_DURATION, info: $info")
}
}
-
- animator.getAnimationHandler().setProvider(null)
}
+ }
- job?.cancel()
+ companion object {
+ private const val MAX_TEST_DURATION = 300L
+ }
+}
+
+class FrameCallbackProvider(val scope: CoroutineScope) : AnimationFrameCallbackProvider {
+ private val callback = MutableSharedFlow<FrameCallback?>(replay = 2)
+ private var frameCount = 0L
+ val frames = MutableStateFlow(frameCount)
+
+ init {
+ scope.launch {
+ callback.collect {
+ withContext(Dispatchers.Main) {
+ delay(1)
+ it?.doFrame(frameCount)
+ }
+ }
+ }
}
override fun postFrameCallback(cb: FrameCallback) {
- frames.value = Pair(frameCount++, cb)
+ frames.value = ++frameCount
+ callback.tryEmit(cb)
}
override fun postCommitCallback(runnable: Runnable) {}
@@ -108,8 +117,4 @@ class KeyguardTransitionRunner(val repository: KeyguardTransitionRepository) :
override fun getFrameDelay() = 1L
override fun setFrameDelay(delay: Long) {}
-
- companion object {
- private const val MAX_TEST_DURATION = 200L
- }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt
new file mode 100644
index 000000000000..984f8fd13cde
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/view/SceneJankMonitorTest.kt
@@ -0,0 +1,206 @@
+/*
+ * 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.scene.ui.view
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.jank.Cuj
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SceneJankMonitorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val underTest: SceneJankMonitor = kosmos.sceneJankMonitorFactory.create()
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(kosmos.testScope)
+ }
+
+ @Test
+ fun onTransitionStart_withProvidedCuj_beginsThatCuj() =
+ kosmos.runTest {
+ val cuj = 1337
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Communal,
+ to = Scenes.Dream,
+ cuj = cuj,
+ )
+ verify(interactionJankMonitor).begin(any(), eq(cuj))
+ verify(interactionJankMonitor, never()).end(anyInt())
+ }
+
+ @Test
+ fun onTransitionEnd_withProvidedCuj_endsThatCuj() =
+ kosmos.runTest {
+ val cuj = 1337
+ underTest.onTransitionEnd(from = Scenes.Communal, to = Scenes.Dream, cuj = cuj)
+ verify(interactionJankMonitor, never()).begin(any(), anyInt())
+ verify(interactionJankMonitor).end(cuj)
+ }
+
+ @Test
+ fun bouncer_authMethodPin() =
+ kosmos.runTest {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodSim() =
+ kosmos.runTest {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Sim,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR,
+ // When the auth method is SIM, unlocking doesn't work like normal. Instead of
+ // leaving the bouncer, the bouncer is switched over to the real authentication
+ // method when the SIM is unlocked.
+ //
+ // Therefore, there's no point in testing this code path and it will, in fact, fail
+ // to unlock.
+ testUnlockedDisappearance = false,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodPattern() =
+ kosmos.runTest {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Pattern,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR,
+ )
+ }
+
+ @Test
+ fun bouncer_authMethodPassword() =
+ kosmos.runTest {
+ bouncer(
+ authenticationMethod = AuthenticationMethodModel.Password,
+ appearCuj = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR,
+ disappearCuj = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR,
+ )
+ }
+
+ private fun Kosmos.bouncer(
+ authenticationMethod: AuthenticationMethodModel,
+ appearCuj: Int,
+ disappearCuj: Int,
+ testUnlockedDisappearance: Boolean = true,
+ ) {
+ // Set up state:
+ fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ runCurrent()
+
+ fun verifyCujCounts(
+ beginAppearCount: Int = 0,
+ beginDisappearCount: Int = 0,
+ endAppearCount: Int = 0,
+ endDisappearCount: Int = 0,
+ ) {
+ verify(interactionJankMonitor, times(beginAppearCount)).begin(any(), eq(appearCuj))
+ verify(interactionJankMonitor, times(beginDisappearCount))
+ .begin(any(), eq(disappearCuj))
+ verify(interactionJankMonitor, times(endAppearCount)).end(appearCuj)
+ verify(interactionJankMonitor, times(endDisappearCount)).end(disappearCuj)
+ }
+
+ // Precondition checks:
+ assertThat(deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked).isFalse()
+ verifyCujCounts()
+
+ // Bouncer appears CUJ:
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Lockscreen,
+ to = Scenes.Bouncer,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Lockscreen, to = Scenes.Bouncer, cuj = null)
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+
+ // Bouncer disappear CUJ but it doesn't log because the device isn't unlocked.
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Bouncer,
+ to = Scenes.Lockscreen,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Bouncer, to = Scenes.Lockscreen, cuj = null)
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1)
+
+ if (!testUnlockedDisappearance) {
+ return
+ }
+
+ // Unlock the device and transition away from the bouncer.
+ fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
+ SuccessFingerprintAuthenticationStatus(0, true)
+ )
+ runCurrent()
+ assertThat(deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked).isTrue()
+
+ // Bouncer disappear CUJ and it doeslog because the device is unlocked.
+ underTest.onTransitionStart(
+ view = mock(),
+ from = Scenes.Bouncer,
+ to = Scenes.Gone,
+ cuj = null,
+ )
+ verifyCujCounts(beginAppearCount = 1, endAppearCount = 1, beginDisappearCount = 1)
+ underTest.onTransitionEnd(from = Scenes.Bouncer, to = Scenes.Gone, cuj = null)
+ verifyCujCounts(
+ beginAppearCount = 1,
+ endAppearCount = 1,
+ beginDisappearCount = 1,
+ endDisappearCount = 1,
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 0e931679ec74..789ca5158dbf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -531,9 +531,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mNotificationPanelViewController = new NotificationPanelViewController(
mView,
- mMainHandler,
- mLayoutInflater,
- mFeatureFlags,
coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
mFalsingManager, new FalsingCollectorFake(),
mKeyguardStateController,
@@ -553,7 +550,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mKeyguardStatusBarViewComponentFactory,
mLockscreenShadeTransitionController,
mScrimController,
- mUserManager,
mMediaDataManager,
mNotificationShadeDepthController,
mAmbientState,
@@ -564,7 +560,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mQsController,
mFragmentService,
mStatusBarService,
- mContentResolver,
mShadeHeaderController,
mScreenOffAnimationController,
mLockscreenGestureLogger,
@@ -575,7 +570,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mKeyguardUnlockAnimationController,
mKeyguardIndicationController,
mNotificationListContainer,
- mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
systemClock,
mKeyguardClockInteractor,
@@ -594,7 +588,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
new ResourcesSplitShadeStateController(),
mPowerInteractor,
mKeyguardClockPositionAlgorithm,
- mNaturalScrollingSettingObserver,
mMSDLPlayer,
mBrightnessMirrorShowingInteractor);
mNotificationPanelViewController.initDependencies(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
index 2e9d6e85d0aa..49cbb5a924f1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplTest.java
@@ -53,7 +53,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qs.flags.QSComposeFragment;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -365,7 +364,6 @@ public class QuickSettingsControllerImplTest extends QuickSettingsControllerImpl
}
@Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void updateExpansion_partiallyExpanded_fullscreenFalse() {
// WHEN QS are only partially expanded
mQsController.setExpanded(true);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
index 83fb14aaf792..6b2c4b260806 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/condition/ConditionExtensionsTest.kt
@@ -9,9 +9,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
@@ -25,7 +24,7 @@ class ConditionExtensionsTest : SysuiTestCase() {
@Before
fun setUp() {
- testScope = TestScope(StandardTestDispatcher())
+ testScope = TestScope(UnconfinedTestDispatcher())
}
@Test
@@ -34,11 +33,9 @@ class ConditionExtensionsTest : SysuiTestCase() {
val flow = flowOf(true)
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
- runCurrent()
assertThat(condition.isConditionSet).isFalse()
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
}
@@ -49,11 +46,9 @@ class ConditionExtensionsTest : SysuiTestCase() {
val flow = flowOf(false)
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
- runCurrent()
assertThat(condition.isConditionSet).isFalse()
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
}
@@ -65,7 +60,6 @@ class ConditionExtensionsTest : SysuiTestCase() {
val condition = flow.toCondition(scope = this, Condition.START_EAGERLY)
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isFalse()
assertThat(condition.isConditionMet).isFalse()
}
@@ -78,11 +72,10 @@ class ConditionExtensionsTest : SysuiTestCase() {
flow.toCondition(
scope = this,
strategy = Condition.START_EAGERLY,
- initialValue = true
+ initialValue = true,
)
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isTrue()
}
@@ -95,11 +88,10 @@ class ConditionExtensionsTest : SysuiTestCase() {
flow.toCondition(
scope = this,
strategy = Condition.START_EAGERLY,
- initialValue = false
+ initialValue = false,
)
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
}
@@ -111,16 +103,13 @@ class ConditionExtensionsTest : SysuiTestCase() {
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
condition.start()
- runCurrent()
assertThat(condition.isConditionSet).isTrue()
assertThat(condition.isConditionMet).isFalse()
flow.value = true
- runCurrent()
assertThat(condition.isConditionMet).isTrue()
flow.value = false
- runCurrent()
assertThat(condition.isConditionMet).isFalse()
condition.stop()
@@ -131,15 +120,12 @@ class ConditionExtensionsTest : SysuiTestCase() {
testScope.runTest {
val flow = MutableSharedFlow<Boolean>()
val condition = flow.toCondition(scope = this, strategy = Condition.START_EAGERLY)
- runCurrent()
assertThat(flow.subscriptionCount.value).isEqualTo(0)
condition.start()
- runCurrent()
assertThat(flow.subscriptionCount.value).isEqualTo(1)
condition.stop()
- runCurrent()
assertThat(flow.subscriptionCount.value).isEqualTo(0)
}
}
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 a62d9d5ce62f..0061c4142e8b 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
@@ -132,7 +132,7 @@ class CallChipViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(
- inCallModel(startTimeMs = 1000, notificationIcon = mock<StatusBarIconView>())
+ inCallModel(startTimeMs = 1000, notificationIcon = createStatusBarIconViewOrNull())
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
@@ -147,11 +147,12 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun chip_positiveStartTime_notifIconFlagOn_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- val notifIcon = mock<StatusBarIconView>()
+ val notifIcon = createStatusBarIconViewOrNull()
repo.setOngoingCallState(inCallModel(startTimeMs = 1000, notificationIcon = notifIcon))
assertThat((latest as OngoingActivityChipModel.Shown).icon)
@@ -165,6 +166,24 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_positiveStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ repo.setOngoingCallState(
+ inCallModel(
+ startTimeMs = 1000,
+ notificationIcon = createStatusBarIconViewOrNull(),
+ notificationKey = "notifKey",
+ )
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ }
+
+ @Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
fun chip_positiveStartTime_notifIconAndConnectedDisplaysFlagOn_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -192,7 +211,7 @@ class CallChipViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chip)
repo.setOngoingCallState(
- inCallModel(startTimeMs = 0, notificationIcon = mock<StatusBarIconView>())
+ inCallModel(startTimeMs = 0, notificationIcon = createStatusBarIconViewOrNull())
)
assertThat((latest as OngoingActivityChipModel.Shown).icon)
@@ -207,11 +226,12 @@ class CallChipViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun chip_zeroStartTime_notifIconFlagOn_iconIsNotifIcon() =
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_zeroStartTime_notifIconFlagOn_cdFlagOff_iconIsNotifIcon() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
- val notifIcon = mock<StatusBarIconView>()
+ val notifIcon = createStatusBarIconViewOrNull()
repo.setOngoingCallState(inCallModel(startTimeMs = 0, notificationIcon = notifIcon))
assertThat((latest as OngoingActivityChipModel.Shown).icon)
@@ -224,8 +244,27 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_zeroStartTime_notifIconFlagOn_cdFlagOn_iconIsNotifKeyIcon() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ repo.setOngoingCallState(
+ inCallModel(
+ startTimeMs = 0,
+ notificationIcon = createStatusBarIconViewOrNull(),
+ notificationKey = "notifKey",
+ )
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ }
+
+ @Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun chip_notifIconFlagOn_butNullNotifIcon_iconIsPhone() =
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_notifIconFlagOn_butNullNotifIcon_cdFlagOff_iconIsPhone() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -242,6 +281,24 @@ class CallChipViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun chip_notifIconFlagOn_butNullNotifIcon_iconNotifKey() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.chip)
+
+ repo.setOngoingCallState(
+ inCallModel(
+ startTimeMs = 1000,
+ notificationIcon = null,
+ notificationKey = "notifKey",
+ )
+ )
+
+ assertThat((latest as OngoingActivityChipModel.Shown).icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon("notifKey"))
+ }
+
+ @Test
fun chip_positiveStartTime_colorsAreThemed() =
testScope.runTest {
val latest by collectLastValue(underTest.chip)
@@ -330,4 +387,13 @@ class CallChipViewModelTest : SysuiTestCase() {
verify(kosmos.activityStarter).postStartActivityDismissingKeyguard(intent, null)
}
+
+ companion object {
+ fun createStatusBarIconViewOrNull(): StatusBarIconView? =
+ if (StatusBarConnectedDisplays.isEnabled) {
+ null
+ } else {
+ mock<StatusBarIconView>()
+ }
+ }
}
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 0d033a4098ec..fe15eac46e2d 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
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.chips.notification.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -148,7 +149,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
- fun notificationChip_missingStatusBarIconChipView_inConstructor_emitsNull() =
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_missingStatusBarIconChipView_cdFlagDisabled_inConstructor_emitsNull() =
kosmos.runTest {
val underTest =
factory.create(
@@ -167,6 +169,25 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_missingStatusBarIconChipView_cdFlagEnabled_inConstructor_emitsNotNull() =
+ kosmos.runTest {
+ val underTest =
+ factory.create(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = null,
+ promotedContent = PROMOTED_CONTENT,
+ ),
+ 32L,
+ )
+
+ val latest by collectLastValue(underTest.notificationChip)
+
+ assertThat(latest).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun notificationChip_cdEnabled_missingStatusBarIconChipView_inConstructor_emitsNotNull() =
kosmos.runTest {
val underTest =
@@ -186,7 +207,8 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
}
@Test
- fun notificationChip_missingStatusBarIconChipView_inSet_emitsNull() =
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_cdFlagDisabled_missingStatusBarIconChipView_inSet_emitsNull() =
kosmos.runTest {
val startingNotif =
activeNotificationModel(
@@ -211,6 +233,31 @@ class SingleNotificationChipInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChip_cdFlagEnabled_missingStatusBarIconChipView_inSet_emitsNotNull() =
+ kosmos.runTest {
+ val startingNotif =
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = mock(),
+ promotedContent = PROMOTED_CONTENT,
+ )
+ val underTest = factory.create(startingNotif, 123L)
+ val latest by collectLastValue(underTest.notificationChip)
+ assertThat(latest).isNotNull()
+
+ underTest.setNotification(
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = null,
+ promotedContent = PROMOTED_CONTENT,
+ )
+ )
+
+ assertThat(latest).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
fun notificationChip_missingStatusBarIconChipView_inSet_cdEnabled_emitsNotNull() =
kosmos.runTest {
val startingNotif =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
index f703d785ceac..ee4a52d35d68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/domain/interactor/StatusBarNotificationChipsInteractorTest.kt
@@ -30,6 +30,7 @@ import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
@@ -83,7 +84,8 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
- fun notificationChips_notifMissingStatusBarChipIconView_empty() =
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOff_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.notificationChips)
@@ -101,6 +103,25 @@ class StatusBarNotificationChipsInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME, StatusBarConnectedDisplays.FLAG_NAME)
+ fun notificationChips_notifMissingStatusBarChipIconView_cdFlagOn_notEmpty() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.notificationChips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ )
+ )
+ )
+
+ assertThat(latest).isNotEmpty()
+ }
+
+ @Test
@EnableFlags(StatusBarNotifChips.FLAG_NAME)
fun notificationChips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
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 17076b4d7505..e561e3ea27d7 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
@@ -23,7 +23,6 @@ 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.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.kosmos.collectLastValue
@@ -31,6 +30,7 @@ import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModelTest.Companion.createStatusBarIconViewOrNull
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.ColorsModel
@@ -48,7 +48,6 @@ import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -84,8 +83,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
- @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
- fun chips_notifMissingStatusBarChipIconView_empty() =
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY, StatusBarConnectedDisplays.FLAG_NAME)
+ fun chips_notifMissingStatusBarChipIconView_cdFlagDisabled_empty() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
@@ -104,11 +103,31 @@ class NotifChipsViewModelTest : SysuiTestCase() {
@Test
@DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
+ @EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun chips_notifMissingStatusBarChipIconView_cdFlagEnabled_notEmpty() =
+ kosmos.runTest {
+ val latest by collectLastValue(underTest.chips)
+
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
+ )
+ )
+ )
+
+ assertThat(latest).isNotEmpty()
+ }
+
+ @Test
+ @DisableFlags(FLAG_PROMOTE_NOTIFICATIONS_AUTOMATICALLY)
fun chips_onePromotedNotif_statusBarIconViewMatches() =
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
- val icon = mock<StatusBarIconView>()
+ val icon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -121,8 +140,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
assertThat(latest).hasSize(1)
val chip = latest!![0]
- assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
- assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon))
+ assertIsNotifChip(chip, icon, "notif")
}
@Test
@@ -168,7 +186,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -187,8 +205,8 @@ class NotifChipsViewModelTest : SysuiTestCase() {
kosmos.runTest {
val latest by collectLastValue(underTest.chips)
- val firstIcon = mock<StatusBarIconView>()
- val secondIcon = mock<StatusBarIconView>()
+ val firstIcon = createStatusBarIconViewOrNull()
+ val secondIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -203,15 +221,15 @@ class NotifChipsViewModelTest : SysuiTestCase() {
),
activeNotificationModel(
key = "notif3",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = null,
),
)
)
assertThat(latest).hasSize(2)
- assertIsNotifChip(latest!![0], firstIcon)
- assertIsNotifChip(latest!![1], secondIcon)
+ assertIsNotifChip(latest!![0], firstIcon, "notif1")
+ assertIsNotifChip(latest!![1], secondIcon, "notif2")
}
@Test
@@ -269,7 +287,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -293,7 +311,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -323,7 +341,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -353,7 +371,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -382,7 +400,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -411,7 +429,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -439,7 +457,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -467,7 +485,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -499,7 +517,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = promotedContentBuilder.build(),
)
)
@@ -531,7 +549,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
listOf(
activeNotificationModel(
key = "clickTest",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent =
PromotedNotificationContentModel.Builder("clickTest").build(),
)
@@ -552,9 +570,21 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
companion object {
- fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) {
- assertThat((latest as OngoingActivityChipModel.Shown).icon)
- .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon))
+ fun assertIsNotifChip(
+ latest: OngoingActivityChipModel?,
+ expectedIcon: StatusBarIconView?,
+ notificationKey: String,
+ ) {
+ val shown = latest as OngoingActivityChipModel.Shown
+ if (StatusBarConnectedDisplays.isEnabled) {
+ assertThat(shown.icon)
+ .isEqualTo(
+ OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon(notificationKey)
+ )
+ } else {
+ assertThat(latest.icon)
+ .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon!!))
+ }
}
fun assertIsNotifKey(latest: OngoingActivityChipModel?, expectedKey: String) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
index 4fb42e94adb2..42358cce59a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.Me
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel
import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.phone.mockSystemUIDialogFactory
import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository
@@ -169,29 +170,35 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
@Test
fun primaryChip_screenRecordAndShareToAppAndCastToOtherHideAndCallShown_callShown() =
testScope.runTest {
+ val notificationKey = "call"
screenRecordState.value = ScreenRecordModel.DoingNothing
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = notificationKey)
+ )
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest)
+ assertIsCallChip(latest, notificationKey)
}
@Test
fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
testScope.runTest {
// Start with just the lowest priority chip shown
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ val callNotificationKey = "call"
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
// And everything else hidden
mediaProjectionState.value = MediaProjectionState.NotProjecting
screenRecordState.value = ScreenRecordModel.DoingNothing
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest)
+ assertIsCallChip(latest, callNotificationKey)
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -218,7 +225,10 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ val callNotificationKey = "call"
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
val latest by collectLastValue(underTest.primaryChip)
@@ -235,7 +245,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN the lower priority call is used
- assertIsCallChip(latest)
+ assertIsCallChip(latest, callNotificationKey)
}
/** Regression test for b/347726238. */
@@ -364,13 +374,27 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() {
assertThat(icon.res).isEqualTo(R.drawable.ic_present_to_all)
}
- fun assertIsCallChip(latest: OngoingActivityChipModel?) {
- assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java)
+ fun assertIsCallChip(latest: OngoingActivityChipModel?, notificationKey: String) {
+ assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Timer::class.java)
+ if (StatusBarConnectedDisplays.isEnabled) {
+ assertNotificationIcon(latest, notificationKey)
+ return
+ }
val icon =
(((latest as OngoingActivityChipModel.Shown).icon)
as OngoingActivityChipModel.ChipIcon.SingleColorIcon)
.impl as Icon.Resource
assertThat(icon.res).isEqualTo(com.android.internal.R.drawable.ic_phone)
}
+
+ private fun assertNotificationIcon(
+ latest: OngoingActivityChipModel?,
+ notificationKey: String,
+ ) {
+ val shown = latest as OngoingActivityChipModel.Shown
+ val notificationIcon =
+ shown.icon as OngoingActivityChipModel.ChipIcon.StatusBarNotificationIcon
+ assertThat(notificationIcon.notificationKey).isEqualTo(notificationKey)
+ }
}
}
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 0050ebee64d6..0f42f29e76ee 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
@@ -34,7 +34,7 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager
import com.android.systemui.res.R
import com.android.systemui.screenrecord.data.model.ScreenRecordModel
import com.android.systemui.screenrecord.data.repository.screenRecordRepository
-import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModelTest.Companion.createStatusBarIconViewOrNull
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE
import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
@@ -186,13 +186,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@Test
fun chips_screenRecordShowAndCallShow_primaryIsScreenRecordSecondaryIsCall() =
testScope.runTest {
+ val callNotificationKey = "call"
screenRecordState.value = ScreenRecordModel.Recording
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
val latest by collectLastValue(underTest.chips)
assertIsScreenRecordChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary)
+ assertIsCallChip(latest!!.secondary, callNotificationKey)
}
@Test
@@ -240,15 +243,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@Test
fun chips_shareToAppShowAndCallShow_primaryIsShareToAppSecondaryIsCall() =
testScope.runTest {
+ val callNotificationKey = "call"
screenRecordState.value = ScreenRecordModel.DoingNothing
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
val latest by collectLastValue(underTest.chips)
assertIsShareToAppChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary)
+ assertIsCallChip(latest!!.secondary, callNotificationKey)
}
@Test
@@ -258,25 +264,31 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ val callNotificationKey = "call"
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
val latest by collectLastValue(underTest.primaryChip)
- assertIsCallChip(latest)
+ assertIsCallChip(latest, callNotificationKey)
}
@Test
fun chips_onlyCallShown_primaryIsCallSecondaryIsHidden() =
testScope.runTest {
+ val callNotificationKey = "call"
screenRecordState.value = ScreenRecordModel.DoingNothing
// MediaProjection covers both share-to-app and cast-to-other-device
mediaProjectionState.value = MediaProjectionState.NotProjecting
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
val latest by collectLastValue(underTest.chips)
- assertIsCallChip(latest!!.primary)
+ assertIsCallChip(latest!!.primary, callNotificationKey)
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@@ -285,7 +297,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- val icon = mock<StatusBarIconView>()
+ val icon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -296,7 +308,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, icon)
+ assertIsNotifChip(latest!!.primary, icon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@@ -305,8 +317,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- val firstIcon = mock<StatusBarIconView>()
- val secondIcon = mock<StatusBarIconView>()
+ val firstIcon = createStatusBarIconViewOrNull()
+ val secondIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -324,8 +336,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, firstIcon)
- assertIsNotifChip(latest!!.secondary, secondIcon)
+ assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
}
@Test
@@ -333,9 +345,9 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- val firstIcon = mock<StatusBarIconView>()
- val secondIcon = mock<StatusBarIconView>()
- val thirdIcon = mock<StatusBarIconView>()
+ val firstIcon = createStatusBarIconViewOrNull()
+ val secondIcon = createStatusBarIconViewOrNull()
+ val thirdIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -359,8 +371,8 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
)
)
- assertIsNotifChip(latest!!.primary, firstIcon)
- assertIsNotifChip(latest!!.secondary, secondIcon)
+ assertIsNotifChip(latest!!.primary, firstIcon, "firstNotif")
+ assertIsNotifChip(latest!!.secondary, secondIcon, "secondNotif")
}
@Test
@@ -368,8 +380,12 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
- val firstIcon = mock<StatusBarIconView>()
+ val callNotificationKey = "call"
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
+
+ val firstIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -380,43 +396,47 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
),
activeNotificationModel(
key = "secondNotif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent =
PromotedNotificationContentModel.Builder("secondNotif").build(),
),
)
)
- assertIsCallChip(latest!!.primary)
- assertIsNotifChip(latest!!.secondary, firstIcon)
+ assertIsCallChip(latest!!.primary, callNotificationKey)
+ assertIsNotifChip(latest!!.secondary, firstIcon, "firstNotif")
}
@Test
fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
testScope.runTest {
+ val callNotificationKey = "call"
val latest by collectLastValue(underTest.chips)
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
screenRecordState.value = ScreenRecordModel.Recording
setNotifs(
listOf(
activeNotificationModel(
key = "notif",
- statusBarChipIcon = mock<StatusBarIconView>(),
+ statusBarChipIcon = createStatusBarIconViewOrNull(),
promotedContent = PromotedNotificationContentModel.Builder("notif").build(),
)
)
)
assertIsScreenRecordChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary)
+ assertIsCallChip(latest!!.secondary, callNotificationKey)
}
@Test
fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() =
testScope.runTest {
+ val callNotificationKey = "call"
// Start with just the lowest priority chip shown
- val notifIcon = mock<StatusBarIconView>()
+ val notifIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -433,13 +453,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.primaryChip)
- assertIsNotifChip(latest, notifIcon)
+ assertIsNotifChip(latest, notifIcon, "notif")
// WHEN the higher priority call chip is added
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
// THEN the higher priority call chip is used
- assertIsCallChip(latest)
+ assertIsCallChip(latest, callNotificationKey)
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -462,12 +484,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
@Test
fun primaryChip_highestPriorityChipRemoved_showsNextPriorityChip() =
testScope.runTest {
+ val callNotificationKey = "call"
// WHEN all chips are active
screenRecordState.value = ScreenRecordModel.Recording
mediaProjectionState.value =
MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE)
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
- val notifIcon = mock<StatusBarIconView>()
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
+ val notifIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -493,20 +518,21 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN the lower priority call is used
- assertIsCallChip(latest)
+ assertIsCallChip(latest, callNotificationKey)
// WHEN the higher priority call is removed
callRepo.setOngoingCallState(OngoingCallModel.NoCall)
// THEN the lower priority notif is used
- assertIsNotifChip(latest, notifIcon)
+ assertIsNotifChip(latest, notifIcon, "notif")
}
@Test
fun chips_movesChipsAroundAccordingToPriority() =
testScope.runTest {
+ val callNotificationKey = "call"
// Start with just the lowest priority chip shown
- val notifIcon = mock<StatusBarIconView>()
+ val notifIcon = createStatusBarIconViewOrNull()
setNotifs(
listOf(
activeNotificationModel(
@@ -523,16 +549,18 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val latest by collectLastValue(underTest.chips)
- assertIsNotifChip(latest!!.primary, notifIcon)
+ assertIsNotifChip(latest!!.primary, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
// WHEN the higher priority call chip is added
- callRepo.setOngoingCallState(inCallModel(startTimeMs = 34))
+ callRepo.setOngoingCallState(
+ inCallModel(startTimeMs = 34, notificationKey = callNotificationKey)
+ )
// THEN the higher priority call chip is used as primary and notif is demoted to
// secondary
- assertIsCallChip(latest!!.primary)
- assertIsNotifChip(latest!!.secondary, notifIcon)
+ assertIsCallChip(latest!!.primary, callNotificationKey)
+ assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
// WHEN the higher priority media projection chip is added
mediaProjectionState.value =
@@ -545,7 +573,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// THEN the higher priority media projection chip is used as primary and call is demoted
// to secondary (and notif is dropped altogether)
assertIsShareToAppChip(latest!!.primary)
- assertIsCallChip(latest!!.secondary)
+ assertIsCallChip(latest!!.secondary, callNotificationKey)
// WHEN the higher priority screen record chip is added
screenRecordState.value = ScreenRecordModel.Recording
@@ -559,13 +587,13 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
// THEN media projection and notif remain
assertIsShareToAppChip(latest!!.primary)
- assertIsNotifChip(latest!!.secondary, notifIcon)
+ assertIsNotifChip(latest!!.secondary, notifIcon, "notif")
// WHEN media projection is dropped
mediaProjectionState.value = MediaProjectionState.NotProjecting
// THEN notif is promoted to primary
- assertIsNotifChip(latest!!.primary, notifIcon)
+ assertIsNotifChip(latest!!.primary, notifIcon, "notif")
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
index dd81b75e180e..1a5f57dd43f8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractorTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.featurepods.media.domain.interactor
+import android.graphics.drawable.Drawable
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -23,12 +24,15 @@ import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
import com.android.systemui.media.controls.shared.model.MediaData
import com.android.systemui.media.controls.shared.model.MediaDataLoadingModel
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -102,4 +106,70 @@ class MediaControlChipInteractorTest : SysuiTestCase() {
assertThat(model?.songName).isEqualTo(newSongName)
}
+
+ @Test
+ fun mediaControlModel_playPauseActionChanges_emitsUpdatedModel() =
+ kosmos.runTest {
+ val model by collectLastValue(underTest.mediaControlModel)
+
+ val mockDrawable = mock<Drawable>()
+
+ val initialAction =
+ MediaAction(
+ icon = mockDrawable,
+ action = {},
+ contentDescription = "Initial Action",
+ background = mockDrawable,
+ )
+ val mediaButton = MediaButton(playOrPause = initialAction)
+ val userMedia = MediaData(active = true, semanticActions = mediaButton)
+ val instanceId = userMedia.instanceId
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(model).isNotNull()
+ assertThat(model?.playOrPause).isEqualTo(initialAction)
+
+ val newAction =
+ MediaAction(
+ icon = mockDrawable,
+ action = {},
+ contentDescription = "New Action",
+ background = mockDrawable,
+ )
+ val updatedMediaButton = MediaButton(playOrPause = newAction)
+ val updatedUserMedia = userMedia.copy(semanticActions = updatedMediaButton)
+ mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+
+ assertThat(model?.playOrPause).isEqualTo(newAction)
+ }
+
+ @Test
+ fun mediaControlModel_playPauseActionRemoved_playPauseNull() =
+ kosmos.runTest {
+ val model by collectLastValue(underTest.mediaControlModel)
+
+ val mockDrawable = mock<Drawable>()
+
+ val initialAction =
+ MediaAction(
+ icon = mockDrawable,
+ action = {},
+ contentDescription = "Initial Action",
+ background = mockDrawable,
+ )
+ val mediaButton = MediaButton(playOrPause = initialAction)
+ val userMedia = MediaData(active = true, semanticActions = mediaButton)
+ val instanceId = userMedia.instanceId
+ mediaFilterRepository.addSelectedUserMediaEntry(userMedia)
+ mediaFilterRepository.addMediaDataLoadingState(MediaDataLoadingModel.Loaded(instanceId))
+
+ assertThat(model).isNotNull()
+ assertThat(model?.playOrPause).isEqualTo(initialAction)
+
+ val updatedUserMedia = userMedia.copy(semanticActions = MediaButton())
+ mediaFilterRepository.addSelectedUserMediaEntry(updatedUserMedia)
+
+ assertThat(model?.playOrPause).isNull()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
index a70d24efada7..912633c874ed 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt
@@ -28,11 +28,11 @@ import com.android.systemui.statusbar.notification.collection.ShadeListBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
+import com.android.systemui.util.mockito.withArgCaptor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.any
-import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.inOrder
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
@@ -59,10 +59,9 @@ class RenderStageManagerTest : SysuiTestCase() {
fun setUp() {
renderStageManager = RenderStageManager()
renderStageManager.attach(shadeListBuilder)
-
- val captor = argumentCaptor<ShadeListBuilder.OnRenderListListener>()
- verify(shadeListBuilder).setOnRenderListListener(captor.capture())
- onRenderListListener = captor.lastValue
+ onRenderListListener = withArgCaptor {
+ verify(shadeListBuilder).setOnRenderListListener(capture())
+ }
}
private fun setUpRenderer() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
index 34f46088ad79..3d5d1eddf581 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModelTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRepository
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.policy.data.repository.zenModeRepository
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
@@ -48,7 +47,6 @@ import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
class EmptyShadeViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
index 615f4b01df9b..daa1db2d49fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.footer.ui.view;
-import static com.android.systemui.log.LogAssertKt.assertLogsWtf;
-
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertFalse;
@@ -34,7 +32,6 @@ import static org.mockito.Mockito.verify;
import android.content.Context;
import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,7 +41,6 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import org.junit.Before;
@@ -62,8 +58,7 @@ public class FooterViewTest extends SysuiTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getFlags() {
- return FlagsParameterization.progressionOf(FooterViewRefactor.FLAG_NAME,
- NotifRedesignFooter.FLAG_NAME);
+ return FlagsParameterization.allCombinationsOf(NotifRedesignFooter.FLAG_NAME);
}
public FooterViewTest(FlagsParameterization flags) {
@@ -106,24 +101,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void setHistoryShown() {
- mView.showHistory(true);
- assertTrue(mView.isHistoryShown());
- assertTrue(((TextView) mView.findViewById(R.id.manage_text))
- .getText().toString().contains("History"));
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void setHistoryNotShown() {
- mView.showHistory(false);
- assertFalse(mView.isHistoryShown());
- assertTrue(((TextView) mView.findViewById(R.id.manage_text))
- .getText().toString().contains("Manage"));
- }
-
- @Test
public void testPerformVisibilityAnimation() {
mView.setVisible(false /* visible */, false /* animate */);
assertFalse(mView.isVisible());
@@ -140,7 +117,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
@DisableFlags(NotifRedesignFooter.FLAG_NAME)
public void testSetManageOrHistoryButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.manage_notifications_history_text;
@@ -160,16 +136,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetManageOrHistoryButtonText_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.string.manage_notifications_history_text;
- assertLogsWtf(() -> mView.setManageOrHistoryButtonText(resId));
- verify(mSpyContext, never()).getString(anyInt());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
@DisableFlags(NotifRedesignFooter.FLAG_NAME)
public void testSetManageOrHistoryButtonDescription_resourceOnlyFetchedOnce() {
int resId = R.string.manage_notifications_history_text;
@@ -189,16 +155,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetManageOrHistoryButtonDescription_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.string.accessibility_clear_all;
- assertLogsWtf(() -> mView.setManageOrHistoryButtonDescription(resId));
- verify(mSpyContext, never()).getString(anyInt());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonText_resourceOnlyFetchedOnce() {
int resId = R.string.clear_all_notifications_text;
mView.setClearAllButtonText(resId);
@@ -217,16 +173,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetClearAllButtonText_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.string.clear_all_notifications_text;
- assertLogsWtf(() -> mView.setClearAllButtonText(resId));
- verify(mSpyContext, never()).getString(anyInt());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() {
int resId = R.string.accessibility_clear_all;
mView.setClearAllButtonDescription(resId);
@@ -245,16 +191,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetClearAllButtonDescription_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.string.accessibility_clear_all;
- assertLogsWtf(() -> mView.setClearAllButtonDescription(resId));
- verify(mSpyContext, never()).getString(anyInt());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetMessageString_resourceOnlyFetchedOnce() {
int resId = R.string.unlock_to_see_notif_text;
mView.setMessageString(resId);
@@ -273,16 +209,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetMessageString_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.string.unlock_to_see_notif_text;
- assertLogsWtf(() -> mView.setMessageString(resId));
- verify(mSpyContext, never()).getString(anyInt());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
public void testSetMessageIcon_resourceOnlyFetchedOnce() {
int resId = R.drawable.ic_friction_lock_closed;
mView.setMessageIcon(resId);
@@ -298,15 +224,6 @@ public class FooterViewTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testSetMessageIcon_expectsFlagEnabled() {
- clearInvocations(mSpyContext);
- int resId = R.drawable.ic_friction_lock_closed;
- assertLogsWtf(() -> mView.setMessageIcon(resId));
- verify(mSpyContext, never()).getDrawable(anyInt());
- }
-
- @Test
public void testSetFooterLabelVisible() {
mView.setFooterLabelVisible(true);
assertThat(mView.findViewById(R.id.unlock_prompt_footer).getVisibility())
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
index 1adfc2b72214..06b1c432955a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt
@@ -40,7 +40,6 @@ import com.android.systemui.shared.settings.data.repository.fakeSecureSettingsRe
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
import com.android.systemui.testKosmos
import com.android.systemui.util.ui.isAnimating
@@ -57,7 +56,6 @@ import platform.test.runner.parameterized.Parameters
@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
class FooterViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index c6cffa9da13b..20cd6c7517e2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -25,14 +25,10 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac
import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -45,7 +41,6 @@ import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper;
import android.view.MotionEvent;
-import android.view.View;
import android.view.ViewTreeObserver;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,15 +52,12 @@ import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
@@ -78,23 +70,18 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -106,11 +93,8 @@ import com.android.systemui.statusbar.notification.stack.ui.viewbinder.Notificat
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.wallpapers.domain.interactor.WallpaperInteractor;
@@ -145,16 +129,13 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private Provider<IStatusBarService> mStatusBarService;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@Mock private TunerService mTunerService;
- @Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
@Mock private ConfigurationController mConfigurationController;
@Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
- @Mock private ZenModeController mZenModeController;
@Mock private KeyguardMediaController mKeyguardMediaController;
@Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private PowerInteractor mPowerInteractor;
- @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@Mock private WallpaperInteractor mWallpaperInteractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
@@ -164,12 +145,10 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
@Mock private NotificationSwipeHelper mNotificationSwipeHelper;
@Mock private GroupExpansionManager mGroupExpansionManager;
- @Mock private SectionHeaderController mSilentHeaderController;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NotifCollection mNotifCollection;
@Mock private UiEventLogger mUiEventLogger;
@Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- @Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
@Mock private Provider<WindowRootView> mWindowRootView;
@@ -193,9 +172,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
- private final SeenNotificationsInteractor mSeenNotificationsInteractor =
- mKosmos.getSeenNotificationsInteractor();
-
private NotificationStackScrollLayoutController mController;
private NotificationTestHelper mNotificationTestHelper;
@@ -279,114 +255,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_notificationsVisible_zenHiding() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(true);
- initController(/* viewIsAttached= */ true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* notifVisibleInShade= */ true);
-
- setupShowEmptyShadeViewState(false);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* notifVisibleInShade= */ true);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_notificationsHidden_zenNotHiding() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* notifVisibleInShade= */ false);
-
- setupShowEmptyShadeViewState(false);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* notifVisibleInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_splitShadeMode_alwaysShowEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- verify(mSysuiStatusBarStateController).addCallback(
- mStateListenerArgumentCaptor.capture(), anyInt());
- StatusBarStateController.StateListener stateListener =
- mStateListenerArgumentCaptor.getValue();
- stateListener.onStateChanged(SHADE);
- mController.getView().removeAllViews();
-
- mController.setQsFullScreen(false);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* notifVisibleInShade= */ false);
-
- mController.setQsFullScreen(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* notifVisibleInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerShowing_hideEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(true);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the PrimaryBouncerInteractor value is used. Since the bouncer is showing, we
- // hide the empty view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ false,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateEmptyShadeView_bouncerNotShowing_showEmptyView() {
- when(mZenModeController.areNotificationsHiddenInShade()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
-
- when(mPrimaryBouncerInteractor.isBouncerShowing()).thenReturn(false);
-
- setupShowEmptyShadeViewState(true);
- reset(mNotificationStackScrollLayout);
- mController.updateShowEmptyShadeView();
-
- // THEN the PrimaryBouncerInteractor value is used. Since the bouncer isn't showing, we
- // can show the empty view.
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(
- /* visible= */ true,
- /* areNotificationsHiddenInShade= */ false);
- }
-
- @Test
public void testOnUserChange_verifyNotSensitive() {
when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
initController(/* viewIsAttached= */ true);
@@ -788,31 +656,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testUpdateFooter_remoteInput() {
- ArgumentCaptor<RemoteInputController.Callback> callbackCaptor =
- ArgumentCaptor.forClass(RemoteInputController.Callback.class);
- doNothing().when(mRemoteInputManager).addControllerCallback(callbackCaptor.capture());
- when(mRemoteInputManager.isRemoteInputActive()).thenReturn(false);
- initController(/* viewIsAttached= */ true);
- verify(mNotificationStackScrollLayout).setIsRemoteInputActive(false);
- RemoteInputController.Callback callback = callbackCaptor.getValue();
- callback.onRemoteInputActive(true);
- verify(mNotificationStackScrollLayout).setIsRemoteInputActive(true);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void testSetNotifStats_updatesHasFilteredOutSeenNotifications() {
- initController(/* viewIsAttached= */ true);
- mSeenNotificationsInteractor.setHasFilteredOutSeenNotifications(true);
- mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
- verify(mNotificationStackScrollLayout).setHasFilteredOutSeenNotifications(true);
- verify(mNotificationStackScrollLayout).updateFooter();
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(anyBoolean(), anyBoolean());
- }
-
- @Test
public void testAttach_updatesViewStatusBarState() {
// GIVEN: Controller is attached
initController(/* viewIsAttached= */ true);
@@ -844,98 +687,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
@Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
- // GIVEN: Controller is attached, active notifications is empty,
- // and mNotificationStackScrollLayout.onKeyguard() is true
- initController(/* viewIsAttached= */ true);
- when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
- mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
-
- // THEN: mNotificationStackScrollLayout should not be important for A11y
- verify(mNotificationStackScrollLayout)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
- // GIVEN: Controller is attached, active notifications is not empty,
- // and mNotificationStackScrollLayout.onKeyguard() is true
- initController(/* viewIsAttached= */ true);
- when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
- mController.getNotifStackController().setNotifStats(
- new NotifStats(
- /* numActiveNotifs = */ 1,
- /* hasNonClearableAlertingNotifs = */ false,
- /* hasClearableAlertingNotifs = */ false,
- /* hasNonClearableSilentNotifs = */ false,
- /* hasClearableSilentNotifs = */ false)
- );
-
- // THEN: mNotificationStackScrollLayout should be important for A11y
- verify(mNotificationStackScrollLayout)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
- // GIVEN: Controller is attached, active notifications is not empty,
- // and mNotificationStackScrollLayout.onKeyguard() is false
- initController(/* viewIsAttached= */ true);
- when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
- mController.getNotifStackController().setNotifStats(
- new NotifStats(
- /* numActiveNotifs = */ 1,
- /* hasNonClearableAlertingNotifs = */ false,
- /* hasClearableAlertingNotifs = */ false,
- /* hasNonClearableSilentNotifs = */ false,
- /* hasClearableSilentNotifs = */ false)
- );
-
- // THEN: mNotificationStackScrollLayout should be important for A11y
- verify(mNotificationStackScrollLayout)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
- // GIVEN: Controller is attached, active notifications is empty,
- // and mNotificationStackScrollLayout.onKeyguard() is false
- initController(/* viewIsAttached= */ true);
- when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
- mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
-
- // THEN: mNotificationStackScrollLayout should be important for A11y
- verify(mNotificationStackScrollLayout)
- .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateEmptyShadeView_onKeyguardTransitionToAod_hidesView() {
- initController(/* viewIsAttached= */ true);
- mController.onKeyguardTransitionChanged(
- new TransitionStep(
- /* from= */ KeyguardState.GONE,
- /* to= */ KeyguardState.AOD));
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
- initController(/* viewIsAttached= */ true);
- mController.onKeyguardTransitionChanged(
- new TransitionStep(
- /* from= */ KeyguardState.OCCLUDED,
- /* to= */ KeyguardState.AOD));
- verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
- }
-
- @Test
@DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
initController(/* viewIsAttached= */ true);
@@ -996,24 +747,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
return argThat(new LogMatcher(category, type));
}
- private void setupShowEmptyShadeViewState(boolean toShow) {
- if (toShow) {
- mController.onKeyguardTransitionChanged(
- new TransitionStep(
- /* from= */ KeyguardState.LOCKSCREEN,
- /* to= */ KeyguardState.GONE));
- mController.setQsFullScreen(false);
- mController.getView().removeAllViews();
- } else {
- mController.onKeyguardTransitionChanged(
- new TransitionStep(
- /* from= */ KeyguardState.GONE,
- /* to= */ KeyguardState.AOD));
- mController.setQsFullScreen(true);
- mController.getView().addContainerView(mock(ExpandableNotificationRow.class));
- }
- }
-
private void initController(boolean viewIsAttached) {
when(mNotificationStackScrollLayout.isAttachedToWindow()).thenReturn(viewIsAttached);
ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
@@ -1033,16 +766,12 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mStatusBarService,
mNotificationRoundnessManager,
mTunerService,
- mDeviceProvisionedController,
mDynamicPrivacyController,
mConfigurationController,
mSysuiStatusBarStateController,
mKeyguardMediaController,
mKeyguardBypassController,
mPowerInteractor,
- mPrimaryBouncerInteractor,
- mKeyguardTransitionRepo,
- mZenModeController,
mNotificationLockscreenUserManager,
mMetricsLogger,
mColorUpdateLogger,
@@ -1051,14 +780,11 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
new FalsingManagerFake(),
mNotificationSwipeHelperBuilder,
mGroupExpansionManager,
- mSilentHeaderController,
mNotifPipeline,
mNotifCollection,
mLockscreenShadeTransitionController,
mUiEventLogger,
- mRemoteInputManager,
mVisibilityLocationProviderDelegator,
- mSeenNotificationsInteractor,
mViewBinder,
mShadeController,
mWindowRootView,
@@ -1076,7 +802,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
}
static class LogMatcher implements ArgumentMatcher<LogMaker> {
- private int mCategory, mType;
+ private final int mCategory, mType;
LogMatcher(int category, int type) {
mCategory = category;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index dcac2941b48b..39cff63f363e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -2,12 +2,10 @@ package com.android.systemui.statusbar.notification.stack
import android.annotation.DimenRes
import android.content.pm.PackageManager
-import android.platform.test.annotations.DisableFlags
import android.platform.test.flag.junit.FlagsParameterization
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
import com.android.systemui.dump.DumpManager
@@ -740,20 +738,6 @@ class StackScrollAlgorithmTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
}
- @DisableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR)
- @Test
- fun resetViewStates_clearAllInProgress_allRowsRemoved_emptyShade_footerHidden() {
- ambientState.isClearAllInProgress = true
- ambientState.isShadeExpanded = true
- ambientState.stackEndHeight = maxPanelHeight // plenty space for the footer in the stack
- hostView.removeAllViews() // remove all rows
- hostView.addView(footerView)
-
- stackScrollAlgorithm.resetViewStates(ambientState, 0)
-
- assertThat((footerView.viewState as FooterViewState).hideContent).isTrue()
- }
-
@Test
fun getGapForLocation_onLockscreen_returnsSmallGap() {
val gap =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
index e592e4b319e3..1b4f9a79557d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt
@@ -18,7 +18,6 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.FlagsParameterization
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -41,7 +40,6 @@ import com.android.systemui.statusbar.notification.data.repository.FakeHeadsUpRo
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.headsup.PinnedStatus
import com.android.systemui.statusbar.notification.stack.data.repository.headsUpNotificationRepository
import com.android.systemui.statusbar.policy.data.repository.fakeUserSetupRepository
@@ -63,7 +61,6 @@ import platform.test.runner.parameterized.Parameters
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
-@EnableFlags(FooterViewRefactor.FLAG_NAME)
class NotificationListViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index d174484219ff..2e12336f6e93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -40,7 +40,6 @@ import static org.mockito.Mockito.when;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.testing.TestableLooper;
@@ -610,7 +609,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_registration() {
/* verify that a predictive back callback is registered when the bouncer becomes visible */
mBouncerExpansionCallback.onVisibilityChanged(true);
@@ -625,7 +623,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_invocationHidesBouncer() {
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
@@ -643,7 +640,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_noBackAnimationForFullScreenBouncer() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.SimPin);
@@ -663,7 +659,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
}
@Test
- @RequiresFlagsEnabled(com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_ANIMATE_BOUNCER)
public void testPredictiveBackCallback_forwardsBackDispatches() {
mBouncerExpansionCallback.onVisibilityChanged(true);
/* capture the predictive back callback during registration */
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
index 0652a835cb7c..650fa7ce46de 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java
@@ -31,7 +31,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Configuration;
-import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.ravenwood.RavenwoodRule;
@@ -41,7 +40,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.Dependency;
-import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.animation.back.BackAnimationSpec;
@@ -137,7 +135,6 @@ public class SystemUIDialogTest extends SysuiTestCase {
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_PREDICTIVE_BACK_ANIMATE_DIALOGS)
public void usePredictiveBackAnimFlag() {
final SystemUIDialog dialog = new SystemUIDialog(mContext);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
index 3d6882c3fdaf..c6bae197ad76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractorTest.kt
@@ -18,9 +18,8 @@ package com.android.systemui.statusbar.policy.domain.interactor
import android.app.AutomaticZenRule
import android.app.Flags
-import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
-import android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY
import android.app.NotificationManager.Policy
+import android.media.AudioManager
import android.platform.test.annotations.EnableFlags
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION
@@ -34,6 +33,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.settingslib.notification.data.repository.updateNotificationPolicy
import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -402,115 +402,124 @@ class ZenModeInteractorTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
- fun activeModesBlockingEverything_hasModesWithFilterNone() =
+ fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
testScope.runTest {
- val blockingEverything by collectLastValue(underTest.activeModesBlockingEverything)
+ val blockingMedia by
+ collectLastValue(
+ underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_MUSIC))
+ )
zenModeRepository.addModes(
listOf(
TestModeBuilder()
- .setName("Filter=None, Not active")
- .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setName("Blocks media, Not active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
.setActive(false)
.build(),
TestModeBuilder()
- .setName("Filter=Priority, Active")
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
+ .setName("Allows media, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Filter=None, Active")
- .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setName("Blocks media, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Filter=None, Active Too")
- .setInterruptionFilter(INTERRUPTION_FILTER_NONE)
+ .setName("Blocks media, Active Too")
+ .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
.setActive(true)
.build(),
)
)
runCurrent()
- assertThat(blockingEverything!!.mainMode!!.name).isEqualTo("Filter=None, Active")
- assertThat(blockingEverything!!.modeNames)
- .containsExactly("Filter=None, Active", "Filter=None, Active Too")
+ assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
+ assertThat(blockingMedia!!.modeNames)
+ .containsExactly("Blocks media, Active", "Blocks media, Active Too")
.inOrder()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
- fun activeModesBlockingMedia_hasModesWithPolicyBlockingMedia() =
+ fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
testScope.runTest {
- val blockingMedia by collectLastValue(underTest.activeModesBlockingMedia)
+ val blockingAlarms by
+ collectLastValue(
+ underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_ALARM))
+ )
zenModeRepository.addModes(
listOf(
TestModeBuilder()
- .setName("Blocks media, Not active")
- .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setName("Blocks alarms, Not active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
.setActive(false)
.build(),
TestModeBuilder()
- .setName("Allows media, Active")
- .setZenPolicy(ZenPolicy.Builder().allowMedia(true).build())
+ .setName("Allows alarms, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Blocks media, Active")
- .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setName("Blocks alarms, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Blocks media, Active Too")
- .setZenPolicy(ZenPolicy.Builder().allowMedia(false).build())
+ .setName("Blocks alarms, Active Too")
+ .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
.setActive(true)
.build(),
)
)
runCurrent()
- assertThat(blockingMedia!!.mainMode!!.name).isEqualTo("Blocks media, Active")
- assertThat(blockingMedia!!.modeNames)
- .containsExactly("Blocks media, Active", "Blocks media, Active Too")
+ assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
+ assertThat(blockingAlarms!!.modeNames)
+ .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too")
.inOrder()
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI)
- fun activeModesBlockingAlarms_hasModesWithPolicyBlockingAlarms() =
+ fun activeModesBlockingAlarms_hasModesWithPolicyBlockingSystem() =
testScope.runTest {
- val blockingAlarms by collectLastValue(underTest.activeModesBlockingAlarms)
+ val blockingSystem by
+ collectLastValue(
+ underTest.activeModesBlockingStream(AudioStream(AudioManager.STREAM_SYSTEM))
+ )
zenModeRepository.addModes(
listOf(
TestModeBuilder()
- .setName("Blocks alarms, Not active")
- .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setName("Blocks system, Not active")
+ .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
.setActive(false)
.build(),
TestModeBuilder()
- .setName("Allows alarms, Active")
- .setZenPolicy(ZenPolicy.Builder().allowAlarms(true).build())
+ .setName("Allows system, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowSystem(true).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Blocks alarms, Active")
- .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setName("Blocks system, Active")
+ .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
.setActive(true)
.build(),
TestModeBuilder()
- .setName("Blocks alarms, Active Too")
- .setZenPolicy(ZenPolicy.Builder().allowAlarms(false).build())
+ .setName("Blocks system, Active Too")
+ .setZenPolicy(ZenPolicy.Builder().allowSystem(false).build())
.setActive(true)
.build(),
)
)
runCurrent()
- assertThat(blockingAlarms!!.mainMode!!.name).isEqualTo("Blocks alarms, Active")
- assertThat(blockingAlarms!!.modeNames)
- .containsExactly("Blocks alarms, Active", "Blocks alarms, Active Too")
+ assertThat(blockingSystem!!.mainMode!!.name).isEqualTo("Blocks system, Active")
+ assertThat(blockingSystem!!.modeNames)
+ .containsExactly("Blocks system, Active", "Blocks system, Active Too")
.inOrder()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
deleted file mode 100644
index 2ad1124d72d4..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/StateTransitionsTest.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.touchpad.tutorial.ui
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
-import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toTutorialActionState
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class StateTransitionsTest : SysuiTestCase() {
-
- companion object {
- private const val START_MARKER = "startMarker"
- private const val END_MARKER = "endMarker"
- private const val SUCCESS_ANIMATION = 0
- }
-
- // needed to simulate caching last state as it's used to create new state
- private var lastState: TutorialActionState = NotStarted
-
- private fun GestureState.toTutorialActionState(): TutorialActionState {
- val newState =
- this.toGestureUiState(
- progressStartMarker = START_MARKER,
- progressEndMarker = END_MARKER,
- successAnimation = SUCCESS_ANIMATION,
- )
- .toTutorialActionState(lastState)
- lastState = newState
- return lastState
- }
-
- @Test
- fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() {
- val happyPath =
- listOf(
- GestureState.NotStarted,
- GestureState.InProgress(0f),
- GestureState.InProgress(0.5f),
- GestureState.InProgress(1f),
- GestureState.Finished,
- )
-
- val resultingStates = mutableListOf<TutorialActionState>()
- happyPath.forEach { resultingStates.add(it.toTutorialActionState()) }
-
- assertThat(resultingStates)
- .containsExactly(
- NotStarted,
- InProgress(0f, START_MARKER, END_MARKER),
- InProgress(0.5f, START_MARKER, END_MARKER),
- InProgress(1f, START_MARKER, END_MARKER),
- Finished(SUCCESS_ANIMATION),
- )
- .inOrder()
- }
-
- @Test
- fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() {
- val errorPath =
- listOf(
- GestureState.NotStarted,
- GestureState.InProgress(0f),
- GestureState.Error,
- GestureState.InProgress(0.5f),
- GestureState.InProgress(1f),
- GestureState.Finished,
- )
-
- val resultingStates = mutableListOf<TutorialActionState>()
- errorPath.forEach { resultingStates.add(it.toTutorialActionState()) }
-
- assertThat(resultingStates)
- .containsExactly(
- NotStarted,
- InProgress(0f, START_MARKER, END_MARKER),
- Error,
- InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)),
- InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)),
- Finished(SUCCESS_ANIMATION),
- )
- .inOrder()
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
index 4aec88e8497b..d752046f4791 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
import com.android.systemui.touchpad.ui.gesture.touchpadGestureResources
@@ -71,8 +71,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
expected =
InProgress(
progress = 1f,
- progressStartMarker = "gesture to L",
- progressEndMarker = "end progress L",
+ startMarker = "gesture to L",
+ endMarker = "end progress L",
),
)
}
@@ -85,8 +85,8 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
expected =
InProgress(
progress = 1f,
- progressStartMarker = "gesture to R",
- progressEndMarker = "end progress R",
+ startMarker = "gesture to R",
+ endMarker = "end progress R",
),
)
}
@@ -114,7 +114,7 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
kosmos.runTest {
fun performBackGesture() =
ThreeFingerGesture.swipeLeft().forEach { viewModel.handleEvent(it) }
- val state by collectLastValue(viewModel.gestureUiState)
+ val state by collectLastValue(viewModel.tutorialState)
performBackGesture()
assertThat(state).isInstanceOf(Finished::class.java)
@@ -134,15 +134,21 @@ class BackGestureScreenViewModelTest : SysuiTestCase() {
fakeConfigRepository.onAnyConfigurationChange()
}
- private fun Kosmos.assertProgressWhileMovingFingers(deltaX: Float, expected: GestureUiState) {
+ private fun Kosmos.assertProgressWhileMovingFingers(
+ deltaX: Float,
+ expected: TutorialActionState,
+ ) {
assertStateAfterEvents(
events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) },
expected = expected,
)
}
- private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
- val state by collectLastValue(viewModel.gestureUiState)
+ private fun Kosmos.assertStateAfterEvents(
+ events: List<MotionEvent>,
+ expected: TutorialActionState,
+ ) {
+ val state by collectLastValue(viewModel.tutorialState)
events.forEach { viewModel.handleEvent(it) }
assertThat(state).isEqualTo(expected)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
index 65a995dcd043..7862fd32ca04 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
@@ -86,8 +86,8 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
expected =
InProgress(
progress = 1f,
- progressStartMarker = "drag with gesture",
- progressEndMarker = "release playback realtime",
+ startMarker = "drag with gesture",
+ endMarker = "release playback realtime",
),
)
}
@@ -108,7 +108,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
@Test
fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() =
kosmos.runTest {
- val state by collectLastValue(viewModel.gestureUiState)
+ val state by collectLastValue(viewModel.tutorialState)
performHomeGesture()
assertThat(state).isInstanceOf(Finished::class.java)
@@ -121,7 +121,7 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
@Test
fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() =
kosmos.runTest {
- val state by collectLastValue(viewModel.gestureUiState)
+ val state by collectLastValue(viewModel.tutorialState)
performHomeGesture()
assertThat(state).isInstanceOf(Finished::class.java)
@@ -147,8 +147,11 @@ class HomeGestureScreenViewModelTest : SysuiTestCase() {
fakeConfigRepository.onAnyConfigurationChange()
}
- private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
- val state by collectLastValue(viewModel.gestureUiState)
+ private fun Kosmos.assertStateAfterEvents(
+ events: List<MotionEvent>,
+ expected: TutorialActionState,
+ ) {
+ val state by collectLastValue(viewModel.tutorialState)
events.forEach { viewModel.handleEvent(it) }
assertThat(state).isEqualTo(expected)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
index 1bc60b67095e..6180fa98b1cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModelTest.kt
@@ -23,16 +23,16 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.inputdevice.tutorial.inputDeviceTutorialLogger
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.res.R
import com.android.systemui.testKosmos
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Error
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.InProgress
import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.SWIPE_DISTANCE
import com.android.systemui.touchpad.tutorial.ui.gesture.ThreeFingerGesture
import com.android.systemui.touchpad.tutorial.ui.gesture.Velocity
@@ -89,8 +89,8 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
expected =
InProgress(
progress = 1f,
- progressStartMarker = "drag with gesture",
- progressEndMarker = "onPause",
+ startMarker = "drag with gesture",
+ endMarker = "onPause",
),
)
}
@@ -111,7 +111,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
@Test
fun gestureRecognitionTakesLatestDistanceThresholdIntoAccount() =
kosmos.runTest {
- val state by collectLastValue(viewModel.gestureUiState)
+ val state by collectLastValue(viewModel.tutorialState)
performRecentAppsGesture()
assertThat(state).isInstanceOf(Finished::class.java)
@@ -124,7 +124,7 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
@Test
fun gestureRecognitionTakesLatestVelocityThresholdIntoAccount() =
kosmos.runTest {
- val state by collectLastValue(viewModel.gestureUiState)
+ val state by collectLastValue(viewModel.tutorialState)
performRecentAppsGesture()
assertThat(state).isInstanceOf(Finished::class.java)
@@ -150,8 +150,11 @@ class RecentAppsGestureScreenViewModelTest : SysuiTestCase() {
fakeConfigRepository.onAnyConfigurationChange()
}
- private fun Kosmos.assertStateAfterEvents(events: List<MotionEvent>, expected: GestureUiState) {
- val state by collectLastValue(viewModel.gestureUiState)
+ private fun Kosmos.assertStateAfterEvents(
+ events: List<MotionEvent>,
+ expected: TutorialActionState,
+ ) {
+ val state by collectLastValue(viewModel.tutorialState)
events.forEach { viewModel.handleEvent(it) }
assertThat(state).isEqualTo(expected)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt
new file mode 100644
index 000000000000..c113dd9e1eff
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModelTest.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Error
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.Finished
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgress
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.InProgressAfterError
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
+import com.android.systemui.kosmos.collectValues
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asFlow
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TouchpadTutorialScreenViewModelTest : SysuiTestCase() {
+
+ companion object {
+ private const val START_MARKER = "startMarker"
+ private const val END_MARKER = "endMarker"
+ private const val SUCCESS_ANIMATION = 0
+ }
+
+ private val kosmos = testKosmos()
+ private val animationProperties =
+ TutorialAnimationProperties(
+ progressStartMarker = START_MARKER,
+ progressEndMarker = END_MARKER,
+ successAnimation = SUCCESS_ANIMATION,
+ )
+
+ @Before
+ fun before() {
+ kosmos.useUnconfinedTestDispatcher()
+ }
+
+ @Test
+ fun gestureStateProducesEquivalentTutorialActionStateInHappyPath() =
+ kosmos.runTest {
+ val happyPath: Flow<Pair<GestureState, TutorialAnimationProperties>> =
+ listOf(
+ GestureState.NotStarted,
+ GestureState.InProgress(0f),
+ GestureState.InProgress(0.5f),
+ GestureState.InProgress(1f),
+ GestureState.Finished,
+ )
+ .map { it to animationProperties }
+ .asFlow()
+
+ val resultingStates by collectValues(happyPath.mapToTutorialState())
+
+ assertThat(resultingStates)
+ .containsExactly(
+ NotStarted,
+ InProgress(0f, START_MARKER, END_MARKER),
+ InProgress(0.5f, START_MARKER, END_MARKER),
+ InProgress(1f, START_MARKER, END_MARKER),
+ Finished(SUCCESS_ANIMATION),
+ )
+ .inOrder()
+ }
+
+ @Test
+ fun gestureStateProducesEquivalentTutorialActionStateInErrorPath() =
+ kosmos.runTest {
+ val errorPath: Flow<Pair<GestureState, TutorialAnimationProperties>> =
+ listOf(
+ GestureState.NotStarted,
+ GestureState.InProgress(0f),
+ GestureState.Error,
+ GestureState.InProgress(0.5f),
+ GestureState.InProgress(1f),
+ GestureState.Finished,
+ )
+ .map { it to animationProperties }
+ .asFlow()
+
+ val resultingStates by collectValues(errorPath.mapToTutorialState())
+
+ assertThat(resultingStates)
+ .containsExactly(
+ NotStarted,
+ InProgress(0f, START_MARKER, END_MARKER),
+ Error,
+ InProgressAfterError(InProgress(0.5f, START_MARKER, END_MARKER)),
+ InProgressAfterError(InProgress(1f, START_MARKER, END_MARKER)),
+ Finished(SUCCESS_ANIMATION),
+ )
+ .inOrder()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
index d3071f87f744..51cac6976362 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelTest.kt
@@ -23,66 +23,40 @@ import android.platform.test.annotations.EnableFlags
import android.service.notification.ZenPolicy
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.internal.logging.uiEventLogger
import com.android.settingslib.notification.modes.TestModeBuilder
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.haptics.slider.sliderHapticsViewModelFactory
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runCurrent
+import com.android.systemui.kosmos.runTest
import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
-import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.testKosmos
-import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
-import com.android.systemui.volume.shared.volumePanelLogger
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.Test
import org.junit.runner.RunWith
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class AudioStreamSliderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testScope = kosmos.testScope
private val zenModeRepository = kosmos.fakeZenModeRepository
- private lateinit var mediaStream: AudioStreamSliderViewModel
- private lateinit var alarmsStream: AudioStreamSliderViewModel
- private lateinit var notificationStream: AudioStreamSliderViewModel
- private lateinit var otherStream: AudioStreamSliderViewModel
-
- @Before
- fun setUp() {
- mediaStream = audioStreamSliderViewModel(AudioManager.STREAM_MUSIC)
- alarmsStream = audioStreamSliderViewModel(AudioManager.STREAM_ALARM)
- notificationStream = audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION)
- otherStream = audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL)
- }
-
- private fun audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel {
- return AudioStreamSliderViewModel(
+ private fun Kosmos.audioStreamSliderViewModel(stream: Int): AudioStreamSliderViewModel {
+ return audioStreamSliderViewModelFactory.create(
AudioStreamSliderViewModel.FactoryAudioStreamWrapper(AudioStream(stream)),
- testScope.backgroundScope,
- context,
- kosmos.audioVolumeInteractor,
- kosmos.zenModeInteractor,
- kosmos.uiEventLogger,
- kosmos.volumePanelLogger,
- kosmos.sliderHapticsViewModelFactory,
+ applicationCoroutineScope,
)
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
fun slider_media_hasDisabledByModesText() =
- testScope.runTest {
- val mediaSlider by collectLastValue(mediaStream.slider)
+ kosmos.runTest {
+ val mediaSlider by
+ collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_MUSIC).slider)
zenModeRepository.addMode(
TestModeBuilder()
@@ -112,8 +86,9 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
fun slider_alarms_hasDisabledByModesText() =
- testScope.runTest {
- val alarmsSlider by collectLastValue(alarmsStream.slider)
+ kosmos.runTest {
+ val alarmsSlider by
+ collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_ALARM).slider)
zenModeRepository.addMode(
TestModeBuilder()
@@ -141,9 +116,10 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
@Test
@EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
- fun slider_other_hasDisabledByModesText() =
- testScope.runTest {
- val otherSlider by collectLastValue(otherStream.slider)
+ fun slider_other_hasDisabledText() =
+ kosmos.runTest {
+ val otherSlider by
+ collectLastValue(audioStreamSliderViewModel(AudioManager.STREAM_VOICE_CALL).slider)
zenModeRepository.addMode(
TestModeBuilder()
@@ -154,20 +130,17 @@ class AudioStreamSliderViewModelTest : SysuiTestCase() {
)
runCurrent()
- assertThat(otherSlider!!.disabledMessage)
- .isEqualTo("Unavailable because Everything blocked is on")
-
- zenModeRepository.clearModes()
- runCurrent()
-
assertThat(otherSlider!!.disabledMessage).isEqualTo("Unavailable")
}
@Test
@EnableFlags(Flags.FLAG_MODES_UI, Flags.FLAG_MODES_UI_ICONS)
fun slider_notification_hasSpecialDisabledText() =
- testScope.runTest {
- val notificationSlider by collectLastValue(notificationStream.slider)
+ kosmos.runTest {
+ val notificationSlider by
+ collectLastValue(
+ audioStreamSliderViewModel(AudioManager.STREAM_NOTIFICATION).slider
+ )
runCurrent()
assertThat(notificationSlider!!.disabledMessage)
diff --git a/packages/SystemUI/res/color/active_track_color.xml b/packages/SystemUI/res/color/active_track_color.xml
new file mode 100644
index 000000000000..232555553d12
--- /dev/null
+++ b/packages/SystemUI/res/color/active_track_color.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" />
+ <item android:alpha="0.38" android:color="@androidprv:color/materialColorOnSurface" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/inactive_track_color.xml b/packages/SystemUI/res/color/inactive_track_color.xml
new file mode 100644
index 000000000000..2ba5ebd8818a
--- /dev/null
+++ b/packages/SystemUI/res/color/inactive_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="@androidprv:color/materialColorSurfaceContainerHighest" android:state_enabled="true" />
+ <item android:alpha="0.12" android:color="@androidprv:color/materialColorOnSurface" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/color/on_active_track_color.xml b/packages/SystemUI/res/color/on_active_track_color.xml
new file mode 100644
index 000000000000..7ca79a9e95af
--- /dev/null
+++ b/packages/SystemUI/res/color/on_active_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="@androidprv:color/materialColorOnPrimary" android:state_enabled="true" />
+ <item android:color="@androidprv:color/materialColorOnSurfaceVariant" />
+</selector>
diff --git a/packages/SystemUI/res/color/on_inactive_track_color.xml b/packages/SystemUI/res/color/on_inactive_track_color.xml
new file mode 100644
index 000000000000..0eb4bfa106fb
--- /dev/null
+++ b/packages/SystemUI/res/color/on_inactive_track_color.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" />
+ <item android:color="@androidprv:color/materialColorOnSurfaceVariant" />
+</selector>
diff --git a/packages/SystemUI/res/color/thumb_color.xml b/packages/SystemUI/res/color/thumb_color.xml
new file mode 100644
index 000000000000..2b0e3a9a072b
--- /dev/null
+++ b/packages/SystemUI/res/color/thumb_color.xml
@@ -0,0 +1,19 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
+ <item android:color="@androidprv:color/materialColorPrimary" android:state_enabled="true" />
+ <item android:alpha="0.38" android:color="@androidprv:color/materialColorOnSurface" />
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/volume_dialog.xml b/packages/SystemUI/res/layout/volume_dialog.xml
index 0b624e1687a6..58f2d3ccc6a8 100644
--- a/packages/SystemUI/res/layout/volume_dialog.xml
+++ b/packages/SystemUI/res/layout/volume_dialog.xml
@@ -44,7 +44,7 @@
app:layout_constraintBottom_toTopOf="@id/volume_dialog_main_slider_container"
app:layout_constraintEnd_toEndOf="@id/volume_dialog_main_slider_container"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"/>
+ app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/volume_dialog_main_slider_container"
diff --git a/packages/SystemUI/res/layout/volume_dialog_slider.xml b/packages/SystemUI/res/layout/volume_dialog_slider.xml
index 967cb3fd68de..6eb7b730e105 100644
--- a/packages/SystemUI/res/layout/volume_dialog_slider.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_slider.xml
@@ -14,8 +14,9 @@
limitations under the License.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height">
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:maxHeight="@dimen/volume_dialog_slider_height">
<com.google.android.material.slider.Slider
android:id="@+id/volume_dialog_slider"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 8bf4e373a6e0..1f7889214bd5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -2116,6 +2116,11 @@
<dimen name="volume_dialog_button_size">40dp</dimen>
<dimen name="volume_dialog_slider_width">52dp</dimen>
<dimen name="volume_dialog_slider_height">254dp</dimen>
+ <!--
+ A primary goal of this margin is to vertically constraint slider height in the landscape
+ orientation when the vertical space is limited
+ -->
+ <dimen name="volume_dialog_slider_vertical_margin">124dp</dimen>
<fraction name="volume_dialog_half_opened_bias">0.2</fraction>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cd37c22c8bc3..a01ff3d5258f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3798,7 +3798,7 @@
<!-- Title at the top of the keyboard shortcut helper UI when in customize mode. The helper
is a component that shows the user which keyboard shortcuts they can use.
[CHAR LIMIT=NONE] -->
- <string name="shortcut_helper_customize_mode_title">Customize keyboard shortcuts</string>
+ <string name="shortcut_helper_customize_mode_title">Customize shortcuts</string>
<!-- Title at the top of the keyboard shortcut helper remove shortcut dialog.
The helper is a component that shows the user which keyboard shortcuts they can use. Also
allows the user to add/remove custom shortcuts.[CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 691fb50a15b8..08891aa65417 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -576,12 +576,12 @@
<style name="SystemUI.Material3.Slider" parent="@style/Widget.Material3.Slider">
<item name="labelStyle">@style/Widget.Material3.Slider.Label</item>
- <item name="thumbColor">@androidprv:color/materialColorPrimary</item>
- <item name="tickColorActive">@androidprv:color/materialColorSurfaceContainerHighest</item>
- <item name="tickColorInactive">@androidprv:color/materialColorPrimary</item>
- <item name="trackColorActive">@androidprv:color/materialColorPrimary</item>
- <item name="trackColorInactive">@androidprv:color/materialColorSurfaceContainerHighest</item>
- <item name="trackIconActiveColor">@androidprv:color/materialColorSurfaceContainerHighest</item>
+ <item name="thumbColor">@color/thumb_color</item>
+ <item name="tickColorActive">@color/on_active_track_color</item>
+ <item name="tickColorInactive">@color/on_inactive_track_color</item>
+ <item name="trackColorActive">@color/active_track_color</item>
+ <item name="trackColorInactive">@color/inactive_track_color</item>
+ <item name="trackIconActiveColor">@color/on_active_track_color</item>
</style>
<style name="Theme.SystemUI.DayNightDialog" parent="@android:style/Theme.DeviceDefault.Light.Dialog"/>
diff --git a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
index 9018e5b7ed92..a8f616c2427d 100644
--- a/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_constraint_set.xml
@@ -6,10 +6,13 @@
<Constraint
android:id="@id/volume_dialog_main_slider_container"
android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_max="@dimen/volume_dialog_slider_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.5" />
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
index 297c38873164..b4d8ae791f36 100644
--- a/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
+++ b/packages/SystemUI/res/xml/volume_dialog_half_folded_constraint_set.xml
@@ -6,10 +6,13 @@
<Constraint
android:id="@id/volume_dialog_main_slider_container"
android:layout_width="@dimen/volume_dialog_slider_width"
- android:layout_height="@dimen/volume_dialog_slider_height"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/volume_dialog_slider_vertical_margin"
android:layout_marginEnd="@dimen/volume_dialog_components_spacing"
+ android:layout_marginBottom="@dimen/volume_dialog_slider_vertical_margin"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_max="@dimen/volume_dialog_slider_height"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="@fraction/volume_dialog_half_opened_bias" />
</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 7d220b505aa0..6e23a0783c9d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -21,11 +21,9 @@ import android.util.Log;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.PictureInPictureSurfaceTransaction;
-import android.window.TaskSnapshot;
import android.window.WindowAnimationState;
import com.android.internal.os.IResultReceiver;
-import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.wm.shell.recents.IRecentsAnimationController;
public class RecentsAnimationControllerCompat {
@@ -40,18 +38,6 @@ public class RecentsAnimationControllerCompat {
mAnimationController = animationController;
}
- public ThumbnailData screenshotTask(int taskId) {
- try {
- final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId);
- if (snapshot != null) {
- return ThumbnailData.fromSnapshot(snapshot);
- }
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to screenshot task", e);
- }
- return new ThumbnailData();
- }
-
public void setInputConsumerEnabled(boolean enabled) {
try {
mAnimationController.setInputConsumerEnabled(enabled);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index d3c02e6f6449..b159a70066ce 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -29,7 +29,6 @@ import com.android.keyguard.domain.interactor.KeyguardKeyboardInteractor;
import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -93,10 +92,8 @@ public class KeyguardPinViewController
mPasswordEntry.setUserActivityListener(this::onUserInput);
mView.onDevicePostureChanged(mPostureController.getDevicePosture());
mPostureController.addCallback(mPostureCallback);
- if (mFeatureFlags.isEnabled(Flags.AUTO_PIN_CONFIRMATION)) {
- mPasswordEntry.setUsePinShapes(true);
- updateAutoConfirmationState();
- }
+ mPasswordEntry.setUsePinShapes(true);
+ updateAutoConfirmationState();
}
protected void onUserInput() {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
deleted file mode 100644
index 554dd6930f7f..000000000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt
+++ /dev/null
@@ -1,66 +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.systemui.bouncer.ui.helper
-
-import androidx.annotation.VisibleForTesting
-
-/** Enumerates all known adaptive layout configurations. */
-enum class BouncerSceneLayout {
- /** The default UI with the bouncer laid out normally. */
- STANDARD_BOUNCER,
- /** The bouncer is displayed vertically stacked with the user switcher. */
- BELOW_USER_SWITCHER,
- /** The bouncer is displayed side-by-side with the user switcher or an empty space. */
- BESIDE_USER_SWITCHER,
- /** The bouncer is split in two with both sides shown side-by-side. */
- SPLIT_BOUNCER,
-}
-
-/** Enumerates the supported window size classes. */
-enum class SizeClass {
- COMPACT,
- MEDIUM,
- EXPANDED,
-}
-
-/**
- * Internal version of `calculateLayout` in the System UI Compose library, extracted here to allow
- * for testing that's not dependent on Compose.
- */
-@VisibleForTesting
-fun calculateLayoutInternal(
- width: SizeClass,
- height: SizeClass,
- isOneHandedModeSupported: Boolean,
-): BouncerSceneLayout {
- return when (height) {
- SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER
- SizeClass.MEDIUM ->
- when (width) {
- SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
- SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER
- SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
- }
- SizeClass.EXPANDED ->
- when (width) {
- SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER
- SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER
- SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER
- }
- }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isOneHandedModeSupported }
- ?: BouncerSceneLayout.STANDARD_BOUNCER
-}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index e02e3fbc339b..10f060c13a59 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -22,10 +22,10 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAK
import android.annotation.MainThread;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Trace;
import android.util.Log;
import android.view.Display;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.internal.util.Preconditions;
import com.android.systemui.dock.DockManager;
import com.android.systemui.doze.dagger.DozeScope;
@@ -314,7 +314,7 @@ public class DozeMachine {
mState = newState;
mDozeLog.traceState(newState);
- Trace.traceCounter(Trace.TRACE_TAG_APP, "doze_machine_state", newState.ordinal());
+ TrackTracer.instantForGroup("keyguard", "doze_machine_state", newState.ordinal());
updatePulseReason(newState, oldState, pulseReason);
performTransitionOnComponents(oldState, newState);
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index 63ac783ad42b..129a6bb72996 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -35,7 +35,6 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.statusbar.notification.collection.SortBySectionTimeFlag
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor
import com.android.systemui.statusbar.notification.shared.NotificationAvalancheSuppression
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -57,7 +56,6 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha
NotificationAvalancheSuppression.token dependsOn VisualInterruptionRefactor.token
PriorityPeopleSection.token dependsOn SortBySectionTimeFlag.token
NotificationMinimalism.token dependsOn NotificationThrottleHun.token
- ModesEmptyShadeFix.token dependsOn FooterViewRefactor.token
ModesEmptyShadeFix.token dependsOn modesUi
// SceneContainer dependencies
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c039e0188064..2c33c0b4403b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -76,21 +76,10 @@ object Flags {
val LOCKSCREEN_CUSTOM_CLOCKS =
resourceBooleanFlag(R.bool.config_enableLockScreenCustomClocks, "lockscreen_custom_clocks")
- /**
- * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
- * will occur in stages. This is one stage of many to come.
- */
- // TODO(b/255607168): Tracking Bug
- @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1")
-
/** Flag to control the revamp of keyguard biometrics progress animation */
// TODO(b/244313043): Tracking bug
@JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
- // flag for controlling auto pin confirmation and material u shapes in bouncer
- @JvmField
- val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
-
/** Enables code to show contextual loyalty cards in wallet entrypoints */
// TODO(b/294110497): Tracking Bug
@JvmField
@@ -100,10 +89,6 @@ object Flags {
// TODO(b/242908637): Tracking Bug
@JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview")
- /** Inflate and bind views upon emitting a blueprint value . */
- // TODO(b/297365780): Tracking Bug
- @JvmField val LAZY_INFLATE_KEYGUARD = releasedFlag("lazy_inflate_keyguard")
-
/** Enables UI updates for AI wallpapers in the wallpaper picker. */
// TODO(b/267722622): Tracking Bug
@JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt
index 19a19d551613..c702ba9f401e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ShortcutHelperCoreStartable.kt
@@ -25,6 +25,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.shortcut.data.repository.CustomInputGesturesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.CommandQueue
@@ -41,6 +42,7 @@ constructor(
private val stateRepository: ShortcutHelperStateRepository,
private val activityStarter: ActivityStarter,
@Background private val backgroundScope: CoroutineScope,
+ private val customInputGesturesRepository: CustomInputGesturesRepository
) : CoreStartable {
override fun start() {
registerBroadcastReceiver(
@@ -55,6 +57,10 @@ constructor(
action = Intent.ACTION_CLOSE_SYSTEM_DIALOGS,
onReceive = { stateRepository.hide() },
)
+ registerBroadcastReceiver(
+ action = Intent.ACTION_USER_SWITCHED,
+ onReceive = { customInputGesturesRepository.refreshCustomInputGestures() },
+ )
commandQueue.addCallback(
object : CommandQueue.Callbacks {
override fun dismissKeyboardShortcutsMenu() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
index 36cd40052041..e5c638cbdfba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/CustomInputGesturesRepository.kt
@@ -25,6 +25,7 @@ import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RES
import android.hardware.input.InputManager.CUSTOM_INPUT_GESTURE_RESULT_SUCCESS
import android.hardware.input.InputSettings
import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult
import com.android.systemui.keyboard.shared.model.ShortcutCustomizationRequestResult.ERROR_OTHER
@@ -37,6 +38,7 @@ import kotlinx.coroutines.withContext
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+@SysUISingleton
class CustomInputGesturesRepository
@Inject
constructor(private val userTracker: UserTracker,
@@ -56,7 +58,7 @@ constructor(private val userTracker: UserTracker,
val customInputGestures =
_customInputGesture.onStart { refreshCustomInputGestures() }
- private fun refreshCustomInputGestures() {
+ fun refreshCustomInputGestures() {
setCustomInputGestures(inputGestures = retrieveCustomInputGestures())
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
index d7be5e622276..e255bdea6100 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/InputGestureMaps.kt
@@ -27,14 +27,19 @@ import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW
import android.hardware.input.KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.AppCategories
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType.MultiTasking
@@ -66,6 +71,11 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to MultiTasking,
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT to MultiTasking,
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to MultiTasking,
+ KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to MultiTasking,
+ KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to MultiTasking,
+ KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to MultiTasking,
+ KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to MultiTasking,
+ KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to MultiTasking,
// App Category
KEY_GESTURE_TYPE_LAUNCH_APPLICATION to AppCategories,
@@ -102,15 +112,23 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
R.string.shortcutHelper_category_split_screen,
KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT to
R.string.shortcutHelper_category_split_screen,
+ KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to
+ R.string.shortcutHelper_category_split_screen,
+ KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to
+ R.string.shortcutHelper_category_split_screen,
+ KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to
+ R.string.shortcutHelper_category_split_screen,
+ KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to
+ R.string.shortcutHelper_category_split_screen,
+ KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to R.string.shortcutHelper_category_split_screen,
// App Category
- KEY_GESTURE_TYPE_LAUNCH_APPLICATION to
- R.string.keyboard_shortcut_group_applications,
+ KEY_GESTURE_TYPE_LAUNCH_APPLICATION to R.string.keyboard_shortcut_group_applications,
)
/**
- * App Category shortcut labels are mapped dynamically based on intent
- * see [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData]
+ * App Category shortcut labels are mapped dynamically based on intent see
+ * [InputGestureDataAdapter.fetchShortcutLabelByAppLaunchData]
*/
val gestureToInternalKeyboardShortcutInfoLabelResIdMap =
mapOf(
@@ -136,6 +154,16 @@ class InputGestureMaps @Inject constructor(private val context: Context) {
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT to R.string.system_multitasking_lhs,
KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT to R.string.system_multitasking_rhs,
KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION to R.string.system_multitasking_full_screen,
+ KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW to
+ R.string.system_desktop_mode_snap_left_window,
+ KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW to
+ R.string.system_desktop_mode_snap_right_window,
+ KEY_GESTURE_TYPE_MINIMIZE_FREEFORM_WINDOW to
+ R.string.system_desktop_mode_minimize_window,
+ KEY_GESTURE_TYPE_TOGGLE_MAXIMIZE_FREEFORM_WINDOW to
+ R.string.system_desktop_mode_toggle_maximize_window,
+ KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY to
+ R.string.system_multitasking_move_to_next_display,
)
val shortcutLabelToKeyGestureTypeMap: Map<String, Int>
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index d40fe468b0a5..591383999182 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -538,27 +538,30 @@ public class KeyguardService extends Service {
@Override // Binder interface
public void onFinishedGoingToSleep(
- @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+ @PowerManager.GoToSleepReason int pmSleepReason, boolean
+ powerButtonLaunchGestureTriggered) {
trace("onFinishedGoingToSleep pmSleepReason=" + pmSleepReason
- + " cameraGestureTriggered=" + cameraGestureTriggered);
+ + " powerButtonLaunchTriggered=" + powerButtonLaunchGestureTriggered);
checkPermission();
mKeyguardViewMediator.onFinishedGoingToSleep(
WindowManagerPolicyConstants.translateSleepReasonToOffReason(pmSleepReason),
- cameraGestureTriggered);
- mPowerInteractor.onFinishedGoingToSleep(cameraGestureTriggered);
+ powerButtonLaunchGestureTriggered);
+ mPowerInteractor.onFinishedGoingToSleep(powerButtonLaunchGestureTriggered);
mKeyguardLifecyclesDispatcher.dispatch(
KeyguardLifecyclesDispatcher.FINISHED_GOING_TO_SLEEP);
}
@Override // Binder interface
public void onStartedWakingUp(
- @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+ @PowerManager.WakeReason int pmWakeReason,
+ boolean powerButtonLaunchGestureTriggered) {
trace("onStartedWakingUp pmWakeReason=" + pmWakeReason
- + " cameraGestureTriggered=" + cameraGestureTriggered);
+ + " powerButtonLaunchGestureTriggered=" + powerButtonLaunchGestureTriggered);
Trace.beginSection("KeyguardService.mBinder#onStartedWakingUp");
checkPermission();
- mKeyguardViewMediator.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
- mPowerInteractor.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+ mKeyguardViewMediator.onStartedWakingUp(pmWakeReason,
+ powerButtonLaunchGestureTriggered);
+ mPowerInteractor.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
mKeyguardLifecyclesDispatcher.dispatch(
KeyguardLifecyclesDispatcher.STARTED_WAKING_UP, pmWakeReason);
Trace.endSection();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 63ac5094c400..647362873015 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -109,6 +109,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.app.animation.Interpolators;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
@@ -813,7 +814,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
return;
}
- if (DEBUG) Log.d(TAG, "keyguardDone");
+ Log.d(TAG, "keyguardDone");
tryKeyguardDone();
}
@@ -832,7 +833,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
@Override
public void keyguardDonePending(int targetUserId) {
Trace.beginSection("KeyguardViewMediator.mViewMediatorCallback#keyguardDonePending");
- if (DEBUG) Log.d(TAG, "keyguardDonePending");
+ Log.d(TAG, "keyguardDonePending");
if (targetUserId != mSelectedUserInteractor.getSelectedUserId()) {
Trace.endSection();
return;
@@ -2735,10 +2736,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private void tryKeyguardDone() {
- if (DEBUG) {
- Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
- + mHideAnimationRun + " animRunning - " + mHideAnimationRunning);
- }
+ Log.d(TAG, "tryKeyguardDone: pending - " + mKeyguardDonePending + ", animRan - "
+ + mHideAnimationRun + " animRunning - " + mHideAnimationRunning);
if (!mKeyguardDonePending && mHideAnimationRun && !mHideAnimationRunning) {
handleKeyguardDone();
} else if (mSurfaceBehindRemoteAnimationRunning) {
@@ -3040,7 +3039,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
private final Runnable mHideAnimationFinishedRunnable = () -> {
- Log.e(TAG, "mHideAnimationFinishedRunnable#run");
+ Log.d(TAG, "mHideAnimationFinishedRunnable#run");
mHideAnimationRunning = false;
tryKeyguardDone();
};
@@ -3983,7 +3982,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
public void setPendingLock(boolean hasPendingLock) {
mPendingLock = hasPendingLock;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0);
+ TrackTracer.instantForGroup("keyguard", "pendingLock", mPendingLock ? 1 : 0);
}
private boolean isViewRootReady() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
index 633628f1167e..c3182003227f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ScreenLifecycle.java
@@ -16,8 +16,7 @@
package com.android.systemui.keyguard;
-import android.os.Trace;
-
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.power.domain.interactor.PowerInteractor;
@@ -80,7 +79,7 @@ public class ScreenLifecycle extends Lifecycle<ScreenLifecycle.Observer> impleme
private void setScreenState(int screenState) {
mScreenState = screenState;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "screenState", screenState);
+ TrackTracer.instantForGroup("screen", "screenState", screenState);
}
public interface Observer {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
index c0ffda6640b2..c261cfefb2b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WakefulnessLifecycle.java
@@ -24,11 +24,11 @@ import android.graphics.Point;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.RemoteException;
-import android.os.Trace;
import android.util.DisplayMetrics;
import androidx.annotation.Nullable;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -197,7 +197,7 @@ public class WakefulnessLifecycle extends Lifecycle<WakefulnessLifecycle.Observe
private void setWakefulness(@Wakefulness int wakefulness) {
mWakefulness = wakefulness;
- Trace.traceCounter(Trace.TRACE_TAG_APP, "wakefulness", wakefulness);
+ TrackTracer.instantForGroup("screen", "wakefulness", wakefulness);
}
private void updateLastWakeOriginLocation() {
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 ac04dd5a7ec1..a39982dd31e7 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
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.graphics.Point
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.widget.LockPatternUtils
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
@@ -64,7 +65,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
/** Defines interface for classes that encapsulate application state for the keyguard. */
interface KeyguardRepository {
@@ -248,13 +248,6 @@ interface KeyguardRepository {
val keyguardDoneAnimationsFinished: Flow<Unit>
/**
- * Receive whether clock should be centered on lockscreen.
- *
- * @deprecated When scene container flag is on use clockShouldBeCentered from domain level.
- */
- val clockShouldBeCentered: Flow<Boolean>
-
- /**
* Whether the primary authentication is required for the given user due to lockdown or
* encryption after reboot.
*/
@@ -306,8 +299,6 @@ interface KeyguardRepository {
suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
- fun setClockShouldBeCentered(shouldBeCentered: Boolean)
-
/**
* Updates signal that the keyguard done animations are finished
*
@@ -390,9 +381,6 @@ constructor(
override val panelAlpha: MutableStateFlow<Float> = MutableStateFlow(1f)
- private val _clockShouldBeCentered = MutableStateFlow(true)
- override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
-
override val topClippingBounds = MutableStateFlow<Int?>(null)
override val isKeyguardShowing: MutableStateFlow<Boolean> =
@@ -681,10 +669,6 @@ constructor(
_isQuickSettingsVisible.value = isVisible
}
- override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
- _clockShouldBeCentered.value = shouldBeCentered
- }
-
override fun setKeyguardEnabled(enabled: Boolean) {
_isKeyguardEnabled.value = enabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 354fc3d82342..24f2493c626d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -212,8 +212,11 @@ constructor(@Main val mainDispatcher: CoroutineDispatcher) : KeyguardTransitionR
Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
return@withContext null
}
+ val isAnimatorRunning = lastAnimator?.isRunning() ?: false
+ val isManualTransitionRunning =
+ updateTransitionId != null && lastStep.transitionState != TransitionState.FINISHED
val startingValue =
- if (lastStep.transitionState != TransitionState.FINISHED) {
+ if (isAnimatorRunning || isManualTransitionRunning) {
Log.i(TAG, "Transition still active: $lastStep, canceling")
when (info.modeOnCanceled) {
TransitionModeOnCanceled.LAST_VALUE -> lastStep.value
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index f792935e67f3..ab5fdd608d03 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -25,6 +25,10 @@ import com.android.systemui.keyguard.data.repository.KeyguardClockRepository
import com.android.systemui.keyguard.shared.model.ClockSize
import com.android.systemui.keyguard.shared.model.ClockSizeSetting
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockId
@@ -39,6 +43,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -117,7 +122,43 @@ constructor(
}
}
} else {
- keyguardInteractor.clockShouldBeCentered
+ combine(
+ shadeInteractor.isShadeLayoutWide,
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ keyguardInteractor.dozeTransitionModel,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to == AOD },
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
+ it.to == LOCKSCREEN
+ },
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map {
+ it.to == DOZING
+ },
+ keyguardInteractor.isPulsing,
+ keyguardTransitionInteractor.startedKeyguardTransitionStep.map { it.to == GONE },
+ ) {
+ isShadeLayoutWide,
+ areAnyNotificationsPresent,
+ dozeTransitionModel,
+ startedToAod,
+ startedToLockScreen,
+ startedToDoze,
+ isPulsing,
+ startedToGone ->
+ when {
+ !isShadeLayoutWide -> true
+ // [areAnyNotificationsPresent] also reacts to notification stack in
+ // homescreen
+ // it may cause unnecessary `false` emission when there's notification in
+ // homescreen
+ // but none in lockscreen when going from GONE to AOD / DOZING
+ // use null to skip emitting wrong value
+ startedToGone || startedToDoze -> null
+ startedToLockScreen -> !areAnyNotificationsPresent
+ startedToAod -> !isPulsing
+ else -> true
+ }
+ }
+ .filterNotNull()
}
fun setClockSize(size: ClockSize) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 0193d7cba616..fbe31bbf36e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -418,8 +418,6 @@ constructor(
initialValue = 0f,
)
- val clockShouldBeCentered: Flow<Boolean> = repository.clockShouldBeCentered
-
/** Whether to animate the next doze mode transition. */
val animateDozingTransitions: Flow<Boolean> by lazy {
if (SceneContainerFlag.isEnabled) {
@@ -485,10 +483,6 @@ constructor(
repository.setAnimateDozingTransitions(animate)
}
- fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
- repository.setClockShouldBeCentered(shouldBeCentered)
- }
-
fun setLastRootViewTapPosition(point: Point?) {
repository.lastRootViewTapPosition.value = point
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
index b4dca5dbcb39..b6395aabde0d 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionDialogDelegate.kt
@@ -58,7 +58,6 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
hostUid,
mediaProjectionMetricsLogger,
defaultSelectedMode,
- dialog,
)
}
@@ -79,7 +78,7 @@ abstract class BaseMediaProjectionPermissionDialogDelegate<T : AlertDialog>(
if (!::viewBinder.isInitialized) {
viewBinder = createViewBinder()
}
- viewBinder.bind()
+ viewBinder.bind(dialog.requireViewById(R.id.screen_share_permission_dialog))
}
private fun updateIcon() {
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
index d23db7c51482..c6e4db7af2d9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/BaseMediaProjectionPermissionViewBinder.kt
@@ -16,7 +16,6 @@
package com.android.systemui.mediaprojection.permission
-import android.app.AlertDialog
import android.content.Context
import android.view.LayoutInflater
import android.view.View
@@ -37,8 +36,8 @@ open class BaseMediaProjectionPermissionViewBinder(
private val hostUid: Int,
private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
@ScreenShareMode val defaultSelectedMode: Int = screenShareOptions.first().mode,
- private val dialog: AlertDialog,
) : AdapterView.OnItemSelectedListener {
+ protected lateinit var containerView: View
private lateinit var warning: TextView
private lateinit var startButton: TextView
private lateinit var screenShareModeSpinner: Spinner
@@ -54,9 +53,10 @@ open class BaseMediaProjectionPermissionViewBinder(
}
}
- open fun bind() {
- warning = dialog.requireViewById(R.id.text_warning)
- startButton = dialog.requireViewById(android.R.id.button1)
+ open fun bind(view: View) {
+ containerView = view
+ warning = containerView.requireViewById(R.id.text_warning)
+ startButton = containerView.requireViewById(android.R.id.button1)
initScreenShareOptions()
createOptionsView(getOptionsViewLayoutId())
}
@@ -67,15 +67,15 @@ open class BaseMediaProjectionPermissionViewBinder(
initScreenShareSpinner()
}
- /** Sets fields on the dialog that change based on which option is selected. */
+ /** Sets fields on the views that change based on which option is selected. */
private fun setOptionSpecificFields() {
warning.text = warningText
startButton.text = startButtonText
}
private fun initScreenShareSpinner() {
- val adapter = OptionsAdapter(dialog.context.applicationContext, screenShareOptions)
- screenShareModeSpinner = dialog.requireViewById(R.id.screen_share_mode_options)
+ val adapter = OptionsAdapter(containerView.context.applicationContext, screenShareOptions)
+ screenShareModeSpinner = containerView.requireViewById(R.id.screen_share_mode_options)
screenShareModeSpinner.adapter = adapter
screenShareModeSpinner.onItemSelectedListener = this
@@ -103,10 +103,10 @@ open class BaseMediaProjectionPermissionViewBinder(
override fun onNothingSelected(parent: AdapterView<*>?) {}
private val warningText: String
- get() = dialog.context.getString(selectedScreenShareOption.warningText, appName)
+ get() = containerView.context.getString(selectedScreenShareOption.warningText, appName)
private val startButtonText: String
- get() = dialog.context.getString(selectedScreenShareOption.startButtonText)
+ get() = containerView.context.getString(selectedScreenShareOption.startButtonText)
fun setStartButtonOnClickListener(listener: View.OnClickListener?) {
startButton.setOnClickListener { view ->
@@ -121,7 +121,7 @@ open class BaseMediaProjectionPermissionViewBinder(
private fun createOptionsView(@LayoutRes layoutId: Int?) {
if (layoutId == null) return
- val stub = dialog.requireViewById<View>(R.id.options_stub) as ViewStub
+ val stub = containerView.requireViewById<View>(R.id.options_stub) as ViewStub
stub.layoutResource = layoutId
stub.inflate()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
index 23210ef0e688..340cb68a83a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
@@ -373,7 +373,6 @@ public class InternetDetailsContentController implements AccessPointController.A
mConnectivityManager.setAirplaneMode(false);
}
- @VisibleForTesting
protected int getDefaultDataSubscriptionId() {
return mSubscriptionManager.getDefaultDataSubscriptionId();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
new file mode 100644
index 000000000000..c64532a2c4ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManager.kt
@@ -0,0 +1,991 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.DialogInterface
+import android.graphics.drawable.Drawable
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.os.Handler
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyDisplayInfo
+import android.text.Html
+import android.text.Layout
+import android.text.TextUtils
+import android.text.method.LinkMovementMethod
+import android.util.Log
+import android.view.View
+import android.view.ViewStub
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.ProgressBar
+import android.widget.Switch
+import android.widget.TextView
+import androidx.annotation.MainThread
+import androidx.annotation.WorkerThread
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.MutableLiveData
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.telephony.flags.Flags
+import com.android.settingslib.satellite.SatelliteDialogUtils.TYPE_IS_WIFI
+import com.android.settingslib.satellite.SatelliteDialogUtils.mayStartSatelliteWarningDialog
+import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
+import com.android.systemui.Prefs
+import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.flags.QsDetailedView
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.wifitrackerlib.WifiEntry
+import com.google.common.annotations.VisibleForTesting
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+
+/**
+ * View content for the Internet tile details that handles all UI interactions and state management.
+ *
+ * @param internetDialog non-null if the details should be shown as part of a dialog and null
+ * otherwise.
+ */
+// TODO: b/377388104 Make this content for details view only.
+class InternetDetailsContentManager
+@AssistedInject
+constructor(
+ private val internetDetailsContentController: InternetDetailsContentController,
+ @Assisted(CAN_CONFIG_MOBILE_DATA) private val canConfigMobileData: Boolean,
+ @Assisted(CAN_CONFIG_WIFI) private val canConfigWifi: Boolean,
+ @Assisted private val coroutineScope: CoroutineScope,
+ @Assisted private var context: Context,
+ @Assisted private var internetDialog: SystemUIDialog?,
+ private val uiEventLogger: UiEventLogger,
+ private val dialogTransitionAnimator: DialogTransitionAnimator,
+ @Main private val handler: Handler,
+ @Background private val backgroundExecutor: Executor,
+ private val keyguard: KeyguardStateController,
+) {
+ // Lifecycle
+ private lateinit var lifecycleRegistry: LifecycleRegistry
+ @VisibleForTesting internal var lifecycleOwner: LifecycleOwner? = null
+ @VisibleForTesting internal val internetContentData = MutableLiveData<InternetContent>()
+ @VisibleForTesting internal var connectedWifiEntry: WifiEntry? = null
+ @VisibleForTesting internal var isProgressBarVisible = false
+
+ // UI Components
+ private lateinit var contentView: View
+ private lateinit var internetDialogTitleView: TextView
+ private lateinit var internetDialogSubTitleView: TextView
+ private lateinit var divider: View
+ private lateinit var progressBar: ProgressBar
+ private lateinit var ethernetLayout: LinearLayout
+ private lateinit var mobileNetworkLayout: LinearLayout
+ private var secondaryMobileNetworkLayout: LinearLayout? = null
+ private lateinit var turnWifiOnLayout: LinearLayout
+ private lateinit var wifiToggleTitleTextView: TextView
+ private lateinit var wifiScanNotifyLayout: LinearLayout
+ private lateinit var wifiScanNotifyTextView: TextView
+ private lateinit var connectedWifiListLayout: LinearLayout
+ private lateinit var connectedWifiIcon: ImageView
+ private lateinit var connectedWifiTitleTextView: TextView
+ private lateinit var connectedWifiSummaryTextView: TextView
+ private lateinit var wifiSettingsIcon: ImageView
+ private lateinit var wifiRecyclerView: RecyclerView
+ private lateinit var seeAllLayout: LinearLayout
+ private lateinit var signalIcon: ImageView
+ private lateinit var mobileTitleTextView: TextView
+ private lateinit var mobileSummaryTextView: TextView
+ private lateinit var airplaneModeSummaryTextView: TextView
+ private lateinit var mobileDataToggle: Switch
+ private lateinit var mobileToggleDivider: View
+ private lateinit var wifiToggle: Switch
+ private lateinit var shareWifiButton: Button
+ private lateinit var airplaneModeButton: Button
+ private var alertDialog: AlertDialog? = null
+ private lateinit var doneButton: Button
+
+ private val canChangeWifiState =
+ WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context)
+ private var wifiNetworkHeight = 0
+ private var backgroundOn: Drawable? = null
+ private var backgroundOff: Drawable? = null
+ private var clickJob: Job? = null
+ private var defaultDataSubId = internetDetailsContentController.defaultDataSubscriptionId
+ @VisibleForTesting
+ internal var adapter = InternetAdapter(internetDetailsContentController, coroutineScope)
+ @VisibleForTesting internal var wifiEntriesCount: Int = 0
+ @VisibleForTesting internal var hasMoreWifiEntries: Boolean = false
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ @Assisted(CAN_CONFIG_MOBILE_DATA) canConfigMobileData: Boolean,
+ @Assisted(CAN_CONFIG_WIFI) canConfigWifi: Boolean,
+ coroutineScope: CoroutineScope,
+ context: Context,
+ internetDialog: SystemUIDialog?,
+ ): InternetDetailsContentManager
+ }
+
+ /**
+ * Binds the content manager to the provided content view.
+ *
+ * This method initializes the lifecycle, views, click listeners, and UI of the details content.
+ * It also updates the UI with the current Wi-Fi network information.
+ *
+ * @param contentView The view to which the content manager should be bound.
+ */
+ fun bind(contentView: View) {
+ if (DEBUG) {
+ Log.d(TAG, "Bind InternetDetailsContentManager")
+ }
+
+ this.contentView = contentView
+
+ initializeLifecycle()
+ initializeViews()
+ updateDetailsUI(getStartingInternetContent())
+ initializeAndConfigure()
+ }
+
+ /**
+ * Initializes the LifecycleRegistry if it hasn't been initialized yet. It sets the initial
+ * state of the LifecycleRegistry to Lifecycle.State.CREATED.
+ */
+ fun initializeLifecycle() {
+ if (!::lifecycleRegistry.isInitialized) {
+ lifecycleOwner =
+ object : LifecycleOwner {
+ override val lifecycle: Lifecycle
+ get() = lifecycleRegistry
+ }
+ lifecycleRegistry = LifecycleRegistry(lifecycleOwner!!)
+ }
+ lifecycleRegistry.currentState = Lifecycle.State.CREATED
+ }
+
+ private fun initializeViews() {
+ // Set accessibility properties
+ contentView.accessibilityPaneTitle =
+ context.getText(R.string.accessibility_desc_quick_settings)
+
+ // Get dimension resources
+ wifiNetworkHeight =
+ context.resources.getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height)
+
+ // Initialize LiveData observer
+ internetContentData.observe(lifecycleOwner!!) { internetContent ->
+ updateDetailsUI(internetContent)
+ }
+
+ // Network layouts
+ internetDialogTitleView = contentView.requireViewById(R.id.internet_dialog_title)
+ internetDialogSubTitleView = contentView.requireViewById(R.id.internet_dialog_subtitle)
+ divider = contentView.requireViewById(R.id.divider)
+ progressBar = contentView.requireViewById(R.id.wifi_searching_progress)
+
+ // Set wifi, mobile and ethernet layouts
+ setWifiLayout()
+ setMobileLayout()
+ ethernetLayout = contentView.requireViewById(R.id.ethernet_layout)
+
+ // Done button is only visible for the dialog view
+ doneButton = contentView.requireViewById(R.id.done_button)
+ if (internetDialog == null) {
+ doneButton.visibility = View.GONE
+ } else {
+ // Set done button if qs details view is not enabled.
+ doneButton.setOnClickListener { internetDialog!!.dismiss() }
+ }
+
+ // Share WiFi
+ shareWifiButton = contentView.requireViewById(R.id.share_wifi_button)
+ shareWifiButton.setOnClickListener { view ->
+ if (
+ internetDetailsContentController.mayLaunchShareWifiSettings(
+ connectedWifiEntry,
+ view,
+ )
+ ) {
+ uiEventLogger.log(InternetDetailsEvent.SHARE_WIFI_QS_BUTTON_CLICKED)
+ }
+ }
+
+ // Airplane mode
+ airplaneModeButton = contentView.requireViewById(R.id.apm_button)
+ airplaneModeButton.setOnClickListener {
+ internetDetailsContentController.setAirplaneModeDisabled()
+ }
+ airplaneModeSummaryTextView = contentView.requireViewById(R.id.airplane_mode_summary)
+
+ // Background drawables
+ backgroundOn = context.getDrawable(R.drawable.settingslib_switch_bar_bg_on)
+ backgroundOff = context.getDrawable(R.drawable.internet_dialog_selected_effect)
+ }
+
+ private fun setWifiLayout() {
+ // Initialize Wi-Fi related views
+ turnWifiOnLayout = contentView.requireViewById(R.id.turn_on_wifi_layout)
+ wifiToggleTitleTextView = contentView.requireViewById(R.id.wifi_toggle_title)
+ wifiScanNotifyLayout = contentView.requireViewById(R.id.wifi_scan_notify_layout)
+ wifiScanNotifyTextView = contentView.requireViewById(R.id.wifi_scan_notify_text)
+ connectedWifiListLayout = contentView.requireViewById(R.id.wifi_connected_layout)
+ connectedWifiIcon = contentView.requireViewById(R.id.wifi_connected_icon)
+ connectedWifiTitleTextView = contentView.requireViewById(R.id.wifi_connected_title)
+ connectedWifiSummaryTextView = contentView.requireViewById(R.id.wifi_connected_summary)
+ wifiSettingsIcon = contentView.requireViewById(R.id.wifi_settings_icon)
+ wifiToggle = contentView.requireViewById(R.id.wifi_toggle)
+ wifiRecyclerView =
+ contentView.requireViewById<RecyclerView>(R.id.wifi_list_layout).apply {
+ layoutManager = LinearLayoutManager(context)
+ adapter = this@InternetDetailsContentManager.adapter
+ }
+ seeAllLayout = contentView.requireViewById(R.id.see_all_layout)
+
+ // Set click listeners for Wi-Fi related views
+ wifiToggle.setOnClickListener {
+ val isChecked = wifiToggle.isChecked
+ handleWifiToggleClicked(isChecked)
+ }
+ connectedWifiListLayout.setOnClickListener(this::onClickConnectedWifi)
+ seeAllLayout.setOnClickListener(this::onClickSeeMoreButton)
+ }
+
+ private fun setMobileLayout() {
+ // Initialize mobile data related views
+ mobileNetworkLayout = contentView.requireViewById(R.id.mobile_network_layout)
+ signalIcon = contentView.requireViewById(R.id.signal_icon)
+ mobileTitleTextView = contentView.requireViewById(R.id.mobile_title)
+ mobileSummaryTextView = contentView.requireViewById(R.id.mobile_summary)
+ mobileDataToggle = contentView.requireViewById(R.id.mobile_toggle)
+ mobileToggleDivider = contentView.requireViewById(R.id.mobile_toggle_divider)
+
+ // Set click listeners for mobile data related views
+ mobileNetworkLayout.setOnClickListener {
+ val autoSwitchNonDdsSubId: Int =
+ internetDetailsContentController.getActiveAutoSwitchNonDdsSubId()
+ if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+ showTurnOffAutoDataSwitchDialog(autoSwitchNonDdsSubId)
+ }
+ internetDetailsContentController.connectCarrierNetwork()
+ }
+
+ // Mobile data toggle
+ mobileDataToggle.setOnClickListener {
+ val isChecked = mobileDataToggle.isChecked
+ if (!isChecked && shouldShowMobileDialog()) {
+ mobileDataToggle.isChecked = true
+ showTurnOffMobileDialog()
+ } else if (internetDetailsContentController.isMobileDataEnabled != isChecked) {
+ internetDetailsContentController.setMobileDataEnabled(
+ context,
+ defaultDataSubId,
+ isChecked,
+ false,
+ )
+ }
+ }
+ }
+
+ /**
+ * This function ensures the component is in the RESUMED state and sets up the internet details
+ * content controller.
+ *
+ * If the component is already in the RESUMED state, this function does nothing.
+ */
+ fun initializeAndConfigure() {
+ // If the current state is RESUMED, it's already initialized.
+ if (lifecycleRegistry.currentState == Lifecycle.State.RESUMED) {
+ return
+ }
+
+ lifecycleRegistry.currentState = Lifecycle.State.RESUMED
+ internetDetailsContentController.onStart(internetDetailsCallback, canConfigWifi)
+ if (!canConfigWifi) {
+ hideWifiViews()
+ }
+ }
+
+ private fun getDialogTitleText(): CharSequence {
+ return internetDetailsContentController.getDialogTitleText()
+ }
+
+ private fun updateDetailsUI(internetContent: InternetContent) {
+ if (DEBUG) {
+ Log.d(TAG, "updateDetailsUI ")
+ }
+ if (QsDetailedView.isEnabled) {
+ internetDialogTitleView.visibility = View.GONE
+ internetDialogSubTitleView.visibility = View.GONE
+ } else {
+ internetDialogTitleView.text = internetContent.internetDialogTitleString
+ internetDialogSubTitleView.text = internetContent.internetDialogSubTitle
+ }
+ airplaneModeButton.visibility =
+ if (internetContent.isAirplaneModeEnabled) View.VISIBLE else View.GONE
+
+ updateEthernetUI(internetContent)
+ updateMobileUI(internetContent)
+ updateWifiUI(internetContent)
+ }
+
+ private fun getStartingInternetContent(): InternetContent {
+ return InternetContent(
+ internetDialogTitleString = getDialogTitleText(),
+ internetDialogSubTitle = getSubtitleText(),
+ isWifiEnabled = internetDetailsContentController.isWifiEnabled,
+ isDeviceLocked = internetDetailsContentController.isDeviceLocked,
+ )
+ }
+
+ private fun getSubtitleText(): String {
+ return internetDetailsContentController.getSubtitleText(isProgressBarVisible).toString()
+ }
+
+ @VisibleForTesting
+ internal fun hideWifiViews() {
+ setProgressBarVisible(false)
+ turnWifiOnLayout.visibility = View.GONE
+ connectedWifiListLayout.visibility = View.GONE
+ wifiRecyclerView.visibility = View.GONE
+ seeAllLayout.visibility = View.GONE
+ shareWifiButton.visibility = View.GONE
+ }
+
+ private fun setProgressBarVisible(visible: Boolean) {
+ if (isProgressBarVisible == visible) {
+ return
+ }
+
+ // Set the indeterminate value from false to true each time to ensure that the progress bar
+ // resets its animation and starts at the leftmost starting point each time it is displayed.
+ isProgressBarVisible = visible
+ progressBar.visibility = if (visible) View.VISIBLE else View.GONE
+ progressBar.isIndeterminate = visible
+ divider.visibility = if (visible) View.GONE else View.VISIBLE
+ internetDialogSubTitleView.text = getSubtitleText()
+ }
+
+ private fun showTurnOffAutoDataSwitchDialog(subId: Int) {
+ var carrierName: CharSequence? = getMobileNetworkTitle(defaultDataSubId)
+ if (TextUtils.isEmpty(carrierName)) {
+ carrierName = getDefaultCarrierName()
+ }
+ alertDialog =
+ AlertDialog.Builder(context)
+ .setTitle(context.getString(R.string.auto_data_switch_disable_title, carrierName))
+ .setMessage(R.string.auto_data_switch_disable_message)
+ .setNegativeButton(R.string.auto_data_switch_dialog_negative_button) { _, _ -> }
+ .setPositiveButton(R.string.auto_data_switch_dialog_positive_button) { _, _ ->
+ internetDetailsContentController.setAutoDataSwitchMobileDataPolicy(
+ subId,
+ /* enable= */ false,
+ )
+ secondaryMobileNetworkLayout?.visibility = View.GONE
+ }
+ .create()
+ alertDialog!!.window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ SystemUIDialog.setShowForAllUsers(alertDialog, true)
+ SystemUIDialog.registerDismissListener(alertDialog)
+ SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing())
+ if (QsDetailedView.isEnabled) {
+ alertDialog!!.show()
+ } else {
+ dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false)
+ Log.e(TAG, "Internet dialog is shown with the refactor code")
+ }
+ }
+
+ private fun shouldShowMobileDialog(): Boolean {
+ val mobileDataTurnedOff =
+ Prefs.getBoolean(context, Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA, false)
+ return internetDetailsContentController.isMobileDataEnabled && !mobileDataTurnedOff
+ }
+
+ private fun getMobileNetworkTitle(subId: Int): CharSequence {
+ return internetDetailsContentController.getMobileNetworkTitle(subId)
+ }
+
+ private fun showTurnOffMobileDialog() {
+ val context = contentView.context
+ var carrierName: CharSequence? = getMobileNetworkTitle(defaultDataSubId)
+ val isInService: Boolean =
+ internetDetailsContentController.isVoiceStateInService(defaultDataSubId)
+ if (TextUtils.isEmpty(carrierName) || !isInService) {
+ carrierName = getDefaultCarrierName()
+ }
+ alertDialog =
+ AlertDialog.Builder(context)
+ .setTitle(R.string.mobile_data_disable_title)
+ .setMessage(context.getString(R.string.mobile_data_disable_message, carrierName))
+ .setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> }
+ .setPositiveButton(
+ com.android.internal.R.string.alert_windows_notification_turn_off_action
+ ) { _: DialogInterface?, _: Int ->
+ internetDetailsContentController.setMobileDataEnabled(
+ context,
+ defaultDataSubId,
+ false,
+ false,
+ )
+ mobileDataToggle.isChecked = false
+ Prefs.putBoolean(context, Prefs.Key.QS_HAS_TURNED_OFF_MOBILE_DATA, true)
+ }
+ .create()
+ alertDialog!!.window?.setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG)
+ SystemUIDialog.setShowForAllUsers(alertDialog, true)
+ SystemUIDialog.registerDismissListener(alertDialog)
+ SystemUIDialog.setWindowOnTop(alertDialog, keyguard.isShowing())
+ if (QsDetailedView.isEnabled) {
+ alertDialog!!.show()
+ } else {
+ dialogTransitionAnimator.showFromDialog(alertDialog!!, internetDialog!!, null, false)
+ }
+ }
+
+ private fun onClickConnectedWifi(view: View?) {
+ if (connectedWifiEntry == null) {
+ return
+ }
+ internetDetailsContentController.launchWifiDetailsSetting(connectedWifiEntry!!.key, view)
+ }
+
+ private fun onClickSeeMoreButton(view: View?) {
+ internetDetailsContentController.launchNetworkSetting(view)
+ }
+
+ private fun handleWifiToggleClicked(isChecked: Boolean) {
+ if (Flags.oemEnabledSatelliteFlag()) {
+ if (clickJob != null && !clickJob!!.isCompleted) {
+ return
+ }
+ clickJob =
+ mayStartSatelliteWarningDialog(contentView.context, coroutineScope, TYPE_IS_WIFI) {
+ isAllowClick: Boolean ->
+ if (isAllowClick) {
+ setWifiEnabled(isChecked)
+ } else {
+ wifiToggle.isChecked = !isChecked
+ }
+ }
+ return
+ }
+ setWifiEnabled(isChecked)
+ }
+
+ private fun setWifiEnabled(isEnabled: Boolean) {
+ if (internetDetailsContentController.isWifiEnabled == isEnabled) {
+ return
+ }
+ internetDetailsContentController.isWifiEnabled = isEnabled
+ }
+
+ @MainThread
+ private fun updateEthernetUI(internetContent: InternetContent) {
+ ethernetLayout.visibility = if (internetContent.hasEthernet) View.VISIBLE else View.GONE
+ }
+
+ private fun updateWifiUI(internetContent: InternetContent) {
+ if (!canConfigWifi) {
+ return
+ }
+
+ updateWifiToggle(internetContent)
+ updateConnectedWifi(internetContent)
+ updateWifiListAndSeeAll(internetContent)
+ updateWifiScanNotify(internetContent)
+ }
+
+ private fun updateMobileUI(internetContent: InternetContent) {
+ if (!internetContent.shouldUpdateMobileNetwork) {
+ return
+ }
+
+ val isNetworkConnected =
+ internetContent.activeNetworkIsCellular || internetContent.isCarrierNetworkActive
+ // 1. Mobile network should be gone if airplane mode ON or the list of active
+ // subscriptionId is null.
+ // 2. Carrier network should be gone if airplane mode ON and Wi-Fi is OFF.
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ /*msg = */ "updateMobileUI, isCarrierNetworkActive = " +
+ internetContent.isCarrierNetworkActive,
+ )
+ }
+
+ if (
+ !internetContent.hasActiveSubIdOnDds &&
+ (!internetContent.isWifiEnabled || !internetContent.isCarrierNetworkActive)
+ ) {
+ mobileNetworkLayout.visibility = View.GONE
+ secondaryMobileNetworkLayout?.visibility = View.GONE
+ return
+ }
+
+ mobileNetworkLayout.visibility = View.VISIBLE
+ mobileDataToggle.setChecked(internetDetailsContentController.isMobileDataEnabled)
+ mobileTitleTextView.text = getMobileNetworkTitle(defaultDataSubId)
+ val summary = getMobileNetworkSummary(defaultDataSubId)
+ if (!TextUtils.isEmpty(summary)) {
+ mobileSummaryTextView.text = Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY)
+ mobileSummaryTextView.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+ mobileSummaryTextView.visibility = View.VISIBLE
+ } else {
+ mobileSummaryTextView.visibility = View.GONE
+ }
+ backgroundExecutor.execute {
+ val drawable = getSignalStrengthDrawable(defaultDataSubId)
+ handler.post { signalIcon.setImageDrawable(drawable) }
+ }
+
+ mobileDataToggle.visibility = if (canConfigMobileData) View.VISIBLE else View.INVISIBLE
+ mobileToggleDivider.visibility = if (canConfigMobileData) View.VISIBLE else View.INVISIBLE
+ val primaryColor =
+ if (isNetworkConnected) R.color.connected_network_primary_color
+ else R.color.disconnected_network_primary_color
+ mobileToggleDivider.setBackgroundColor(context.getColor(primaryColor))
+
+ // Display the info for the non-DDS if it's actively being used
+ val autoSwitchNonDdsSubId: Int = internetContent.activeAutoSwitchNonDdsSubId
+
+ val nonDdsVisibility =
+ if (autoSwitchNonDdsSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) View.VISIBLE
+ else View.GONE
+
+ val secondaryRes =
+ if (isNetworkConnected) R.style.TextAppearance_InternetDialog_Secondary_Active
+ else R.style.TextAppearance_InternetDialog_Secondary
+ if (nonDdsVisibility == View.VISIBLE) {
+ // non DDS is the currently active sub, set primary visual for it
+ setNonDDSActive(autoSwitchNonDdsSubId)
+ } else {
+ mobileNetworkLayout.background = if (isNetworkConnected) backgroundOn else backgroundOff
+ mobileTitleTextView.setTextAppearance(
+ if (isNetworkConnected) R.style.TextAppearance_InternetDialog_Active
+ else R.style.TextAppearance_InternetDialog
+ )
+ mobileSummaryTextView.setTextAppearance(secondaryRes)
+ }
+
+ secondaryMobileNetworkLayout?.visibility = nonDdsVisibility
+
+ // Set airplane mode to the summary for carrier network
+ if (internetContent.isAirplaneModeEnabled) {
+ airplaneModeSummaryTextView.apply {
+ visibility = View.VISIBLE
+ text = context.getText(R.string.airplane_mode)
+ setTextAppearance(secondaryRes)
+ }
+ } else {
+ airplaneModeSummaryTextView.visibility = View.GONE
+ }
+ }
+
+ private fun setNonDDSActive(autoSwitchNonDdsSubId: Int) {
+ val stub: ViewStub = contentView.findViewById(R.id.secondary_mobile_network_stub)
+ stub.inflate()
+ secondaryMobileNetworkLayout =
+ contentView.findViewById(R.id.secondary_mobile_network_layout)
+ secondaryMobileNetworkLayout?.setOnClickListener { view: View? ->
+ this.onClickConnectedSecondarySub(view)
+ }
+ secondaryMobileNetworkLayout?.background = backgroundOn
+
+ contentView.requireViewById<TextView>(R.id.secondary_mobile_title).apply {
+ text = getMobileNetworkTitle(autoSwitchNonDdsSubId)
+ setTextAppearance(R.style.TextAppearance_InternetDialog_Active)
+ }
+
+ val summary = getMobileNetworkSummary(autoSwitchNonDdsSubId)
+ contentView.requireViewById<TextView>(R.id.secondary_mobile_summary).apply {
+ if (!TextUtils.isEmpty(summary)) {
+ text = Html.fromHtml(summary, Html.FROM_HTML_MODE_LEGACY)
+ breakStrategy = Layout.BREAK_STRATEGY_SIMPLE
+ setTextAppearance(R.style.TextAppearance_InternetDialog_Active)
+ }
+ }
+
+ val secondarySignalIcon: ImageView = contentView.requireViewById(R.id.secondary_signal_icon)
+ backgroundExecutor.execute {
+ val drawable = getSignalStrengthDrawable(autoSwitchNonDdsSubId)
+ handler.post { secondarySignalIcon.setImageDrawable(drawable) }
+ }
+
+ contentView.requireViewById<ImageView>(R.id.secondary_settings_icon).apply {
+ setColorFilter(context.getColor(R.color.connected_network_primary_color))
+ }
+
+ // set secondary visual for default data sub
+ mobileNetworkLayout.background = backgroundOff
+ mobileTitleTextView.setTextAppearance(R.style.TextAppearance_InternetDialog)
+ mobileSummaryTextView.setTextAppearance(R.style.TextAppearance_InternetDialog_Secondary)
+ signalIcon.setColorFilter(context.getColor(R.color.connected_network_secondary_color))
+ }
+
+ @MainThread
+ private fun updateWifiToggle(internetContent: InternetContent) {
+ if (wifiToggle.isChecked != internetContent.isWifiEnabled) {
+ wifiToggle.isChecked = internetContent.isWifiEnabled
+ }
+ if (internetContent.isDeviceLocked) {
+ wifiToggleTitleTextView.setTextAppearance(
+ if ((connectedWifiEntry != null)) R.style.TextAppearance_InternetDialog_Active
+ else R.style.TextAppearance_InternetDialog
+ )
+ }
+ turnWifiOnLayout.background =
+ if ((internetContent.isDeviceLocked && connectedWifiEntry != null)) backgroundOn
+ else null
+
+ if (!canChangeWifiState && wifiToggle.isEnabled) {
+ wifiToggle.isEnabled = false
+ wifiToggleTitleTextView.isEnabled = false
+ contentView.requireViewById<TextView>(R.id.wifi_toggle_summary).apply {
+ isEnabled = false
+ visibility = View.VISIBLE
+ }
+ }
+ }
+
+ @MainThread
+ private fun updateConnectedWifi(internetContent: InternetContent) {
+ if (
+ !internetContent.isWifiEnabled ||
+ connectedWifiEntry == null ||
+ internetContent.isDeviceLocked
+ ) {
+ connectedWifiListLayout.visibility = View.GONE
+ shareWifiButton.visibility = View.GONE
+ return
+ }
+ connectedWifiListLayout.visibility = View.VISIBLE
+ connectedWifiTitleTextView.text = connectedWifiEntry!!.title
+ connectedWifiSummaryTextView.text = connectedWifiEntry!!.getSummary(false)
+ connectedWifiIcon.setImageDrawable(
+ internetDetailsContentController.getInternetWifiDrawable(connectedWifiEntry!!)
+ )
+ wifiSettingsIcon.setColorFilter(context.getColor(R.color.connected_network_primary_color))
+
+ val canShareWifi =
+ internetDetailsContentController.getConfiguratorQrCodeGeneratorIntentOrNull(
+ connectedWifiEntry
+ ) != null
+ shareWifiButton.visibility = if (canShareWifi) View.VISIBLE else View.GONE
+
+ secondaryMobileNetworkLayout?.visibility = View.GONE
+ }
+
+ @MainThread
+ private fun updateWifiListAndSeeAll(internetContent: InternetContent) {
+ if (!internetContent.isWifiEnabled || internetContent.isDeviceLocked) {
+ wifiRecyclerView.visibility = View.GONE
+ seeAllLayout.visibility = View.GONE
+ return
+ }
+ val wifiListMaxCount = getWifiListMaxCount()
+ if (adapter.itemCount > wifiListMaxCount) {
+ hasMoreWifiEntries = true
+ }
+ adapter.setMaxEntriesCount(wifiListMaxCount)
+ val wifiListMinHeight = wifiNetworkHeight * wifiListMaxCount
+ if (wifiRecyclerView.minimumHeight != wifiListMinHeight) {
+ wifiRecyclerView.minimumHeight = wifiListMinHeight
+ }
+ wifiRecyclerView.visibility = View.VISIBLE
+ seeAllLayout.visibility = if (hasMoreWifiEntries) View.VISIBLE else View.INVISIBLE
+ }
+
+ @MainThread
+ private fun updateWifiScanNotify(internetContent: InternetContent) {
+ if (
+ internetContent.isWifiEnabled ||
+ !internetContent.isWifiScanEnabled ||
+ internetContent.isDeviceLocked
+ ) {
+ wifiScanNotifyLayout.visibility = View.GONE
+ return
+ }
+
+ if (TextUtils.isEmpty(wifiScanNotifyTextView.text)) {
+ val linkInfo =
+ AnnotationLinkSpan.LinkInfo(AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION) {
+ view: View? ->
+ internetDetailsContentController.launchWifiScanningSetting(view)
+ }
+ wifiScanNotifyTextView.text =
+ AnnotationLinkSpan.linkify(
+ context.getText(R.string.wifi_scan_notify_message),
+ linkInfo,
+ )
+ wifiScanNotifyTextView.movementMethod = LinkMovementMethod.getInstance()
+ }
+ wifiScanNotifyLayout.visibility = View.VISIBLE
+ }
+
+ @VisibleForTesting
+ @MainThread
+ internal fun getWifiListMaxCount(): Int {
+ // Use the maximum count of networks to calculate the remaining count for Wi-Fi networks.
+ var count = MAX_NETWORK_COUNT
+ if (ethernetLayout.visibility == View.VISIBLE) {
+ count -= 1
+ }
+ if (mobileNetworkLayout.visibility == View.VISIBLE) {
+ count -= 1
+ }
+
+ // If the remaining count is greater than the maximum count of the Wi-Fi network, the
+ // maximum count of the Wi-Fi network is used.
+ if (count > InternetDetailsContentController.MAX_WIFI_ENTRY_COUNT) {
+ count = InternetDetailsContentController.MAX_WIFI_ENTRY_COUNT
+ }
+ if (connectedWifiListLayout.visibility == View.VISIBLE) {
+ count -= 1
+ }
+ return count
+ }
+
+ private fun getMobileNetworkSummary(subId: Int): String {
+ return internetDetailsContentController.getMobileNetworkSummary(subId)
+ }
+
+ /** For DSDS auto data switch */
+ private fun onClickConnectedSecondarySub(view: View?) {
+ internetDetailsContentController.launchMobileNetworkSettings(view)
+ }
+
+ private fun getSignalStrengthDrawable(subId: Int): Drawable {
+ return internetDetailsContentController.getSignalStrengthDrawable(subId)
+ }
+
+ /**
+ * Unbinds all listeners and resources associated with the view. This method should be called
+ * when the view is no longer needed.
+ */
+ fun unBind() {
+ if (DEBUG) {
+ Log.d(TAG, "unBind")
+ }
+ lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
+ mobileNetworkLayout.setOnClickListener(null)
+ connectedWifiListLayout.setOnClickListener(null)
+ secondaryMobileNetworkLayout?.setOnClickListener(null)
+ seeAllLayout.setOnClickListener(null)
+ wifiToggle.setOnCheckedChangeListener(null)
+ doneButton.setOnClickListener(null)
+ shareWifiButton.setOnClickListener(null)
+ airplaneModeButton.setOnClickListener(null)
+ internetDetailsContentController.onStop()
+ }
+
+ /**
+ * Update the internet details content when receiving the callback.
+ *
+ * @param shouldUpdateMobileNetwork `true` for update the mobile network layout, otherwise
+ * `false`.
+ */
+ @VisibleForTesting
+ internal fun updateContent(shouldUpdateMobileNetwork: Boolean) {
+ backgroundExecutor.execute {
+ internetContentData.postValue(getInternetContent(shouldUpdateMobileNetwork))
+ }
+ }
+
+ private fun getInternetContent(shouldUpdateMobileNetwork: Boolean): InternetContent {
+ return InternetContent(
+ shouldUpdateMobileNetwork = shouldUpdateMobileNetwork,
+ internetDialogTitleString = getDialogTitleText(),
+ internetDialogSubTitle = getSubtitleText(),
+ activeNetworkIsCellular =
+ if (shouldUpdateMobileNetwork)
+ internetDetailsContentController.activeNetworkIsCellular()
+ else false,
+ isCarrierNetworkActive =
+ if (shouldUpdateMobileNetwork)
+ internetDetailsContentController.isCarrierNetworkActive()
+ else false,
+ isAirplaneModeEnabled = internetDetailsContentController.isAirplaneModeEnabled,
+ hasEthernet = internetDetailsContentController.hasEthernet(),
+ isWifiEnabled = internetDetailsContentController.isWifiEnabled,
+ hasActiveSubIdOnDds = internetDetailsContentController.hasActiveSubIdOnDds(),
+ isDeviceLocked = internetDetailsContentController.isDeviceLocked,
+ isWifiScanEnabled = internetDetailsContentController.isWifiScanEnabled(),
+ activeAutoSwitchNonDdsSubId =
+ internetDetailsContentController.getActiveAutoSwitchNonDdsSubId(),
+ )
+ }
+
+ /**
+ * Handles window focus changes. If the activity loses focus and the system UI dialog is
+ * showing, it dismisses the current alert dialog to prevent it from persisting in the
+ * background.
+ *
+ * @param dialog The internet system UI dialog whose focus state has changed.
+ * @param hasFocus True if the window has gained focus, false otherwise.
+ */
+ fun onWindowFocusChanged(dialog: SystemUIDialog, hasFocus: Boolean) {
+ if (alertDialog != null && !alertDialog!!.isShowing) {
+ if (!hasFocus && dialog.isShowing) {
+ dialog.dismiss()
+ }
+ }
+ }
+
+ private fun getDefaultCarrierName(): String? {
+ return context.getString(R.string.mobile_data_disable_message_default_carrier)
+ }
+
+ @VisibleForTesting
+ internal val internetDetailsCallback =
+ object : InternetDetailsContentController.InternetDialogCallback {
+ override fun onRefreshCarrierInfo() {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onSimStateChanged() {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ @WorkerThread
+ override fun onCapabilitiesChanged(
+ network: Network?,
+ networkCapabilities: NetworkCapabilities?,
+ ) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ @WorkerThread
+ override fun onLost(network: Network) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onSubscriptionsChanged(dataSubId: Int) {
+ defaultDataSubId = dataSubId
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onServiceStateChanged(serviceState: ServiceState?) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ @WorkerThread
+ override fun onDataConnectionStateChanged(state: Int, networkType: Int) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onSignalStrengthsChanged(signalStrength: SignalStrength?) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onUserMobileDataStateChanged(enabled: Boolean) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onDisplayInfoChanged(telephonyDisplayInfo: TelephonyDisplayInfo?) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun onCarrierNetworkChange(active: Boolean) {
+ updateContent(shouldUpdateMobileNetwork = true)
+ }
+
+ override fun dismissDialog() {
+ if (DEBUG) {
+ Log.d(TAG, "dismissDialog")
+ }
+ if (internetDialog != null) {
+ internetDialog!!.dismiss()
+ internetDialog = null
+ }
+ }
+
+ override fun onAccessPointsChanged(
+ wifiEntries: MutableList<WifiEntry>?,
+ connectedEntry: WifiEntry?,
+ ifHasMoreWifiEntries: Boolean,
+ ) {
+ // Should update the carrier network layout when it is connected under airplane
+ // mode ON.
+ val shouldUpdateCarrierNetwork =
+ (mobileNetworkLayout.visibility == View.VISIBLE) &&
+ internetDetailsContentController.isAirplaneModeEnabled
+ handler.post {
+ connectedWifiEntry = connectedEntry
+ wifiEntriesCount = wifiEntries?.size ?: 0
+ hasMoreWifiEntries = ifHasMoreWifiEntries
+ updateContent(shouldUpdateCarrierNetwork)
+ adapter.setWifiEntries(wifiEntries, wifiEntriesCount)
+ adapter.notifyDataSetChanged()
+ }
+ }
+
+ override fun onWifiScan(isScan: Boolean) {
+ setProgressBarVisible(isScan)
+ }
+ }
+
+ enum class InternetDetailsEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The Internet details became visible on the screen.")
+ INTERNET_DETAILS_VISIBLE(2071),
+ @UiEvent(doc = "The share wifi button is clicked.") SHARE_WIFI_QS_BUTTON_CLICKED(1462);
+
+ override fun getId(): Int {
+ return id
+ }
+ }
+
+ @VisibleForTesting
+ data class InternetContent(
+ val internetDialogTitleString: CharSequence,
+ val internetDialogSubTitle: CharSequence,
+ val isAirplaneModeEnabled: Boolean = false,
+ val hasEthernet: Boolean = false,
+ val shouldUpdateMobileNetwork: Boolean = false,
+ val activeNetworkIsCellular: Boolean = false,
+ val isCarrierNetworkActive: Boolean = false,
+ val isWifiEnabled: Boolean = false,
+ val hasActiveSubIdOnDds: Boolean = false,
+ val isDeviceLocked: Boolean = false,
+ val isWifiScanEnabled: Boolean = false,
+ val activeAutoSwitchNonDdsSubId: Int = SubscriptionManager.INVALID_SUBSCRIPTION_ID,
+ )
+
+ companion object {
+ private const val TAG = "InternetDetailsContent"
+ private val DEBUG: Boolean = Log.isLoggable(TAG, Log.DEBUG)
+ private const val MAX_NETWORK_COUNT = 4
+ const val CAN_CONFIG_MOBILE_DATA = "can_config_mobile_data"
+ const val CAN_CONFIG_WIFI = "can_config_wifi"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
index f239a179d79a..f239a179d79a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsViewModel.kt
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
index ee53471253af..a418b2a71f50 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateLegacy.java
@@ -70,6 +70,7 @@ import com.android.systemui.accessibility.floatingmenu.AnnotationLinkSpan;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.qs.flags.QsDetailedView;
import com.android.systemui.res.R;
import com.android.systemui.shade.ShadeDisplayAware;
import com.android.systemui.shade.domain.interactor.ShadeDialogContextInteractor;
@@ -207,7 +208,8 @@ public class InternetDialogDelegateLegacy implements
KeyguardStateController keyguardStateController,
SystemUIDialog.Factory systemUIDialogFactory,
ShadeDialogContextInteractor shadeDialogContextInteractor) {
- // TODO: b/377388104 QsDetailedView.assertInLegacyMode();
+ // If `QsDetailedView` is enabled, it should show the details view.
+ QsDetailedView.assertInLegacyMode();
mAboveStatusBar = aboveStatusBar;
mSystemUIDialogFactory = systemUIDialogFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
index 8a54648f4541..5f82e60b63ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt
@@ -23,6 +23,7 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.coroutines.newTracingContext
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.flags.QsDetailedView
import com.android.systemui.statusbar.phone.SystemUIDialog
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -42,21 +43,24 @@ constructor(
@Background private val bgDispatcher: CoroutineDispatcher,
) {
private lateinit var coroutineScope: CoroutineScope
+
companion object {
private const val INTERACTION_JANK_TAG = "internet"
var dialog: SystemUIDialog? = null
}
/**
- * Creates a [InternetDialogDelegateLegacy]. The dialog will be animated from [expandable] if
- * it is not null.
+ * Creates a [InternetDialogDelegateLegacy]. The dialog will be animated from [expandable] if it
+ * is not null.
*/
fun create(
aboveStatusBar: Boolean,
canConfigMobileData: Boolean,
canConfigWifi: Boolean,
- expandable: Expandable?
+ expandable: Expandable?,
) {
+ // If `QsDetailedView` is enabled, it should show the details view.
+ QsDetailedView.assertInLegacyMode()
if (dialog != null) {
if (DEBUG) {
Log.d(TAG, "InternetDialog is showing, do not create it twice.")
@@ -64,11 +68,11 @@ constructor(
return
} else {
coroutineScope = CoroutineScope(bgDispatcher + newTracingContext("InternetDialogScope"))
- // TODO: b/377388104 check the QsDetailedView flag to use the correct dialogFactory
dialog =
dialogFactory
.create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope)
.createDialog()
+
val controller =
expandable?.dialogTransitionController(
DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)
@@ -77,10 +81,9 @@ constructor(
dialogTransitionAnimator.show(
dialog!!,
controller,
- animateBackgroundBoundsChange = true
+ animateBackgroundBoundsChange = true,
)
- }
- ?: dialog?.show()
+ } ?: dialog?.show()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt
new file mode 100644
index 000000000000..48a49c60d8a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneJankMonitor.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.scene.ui.view
+
+import android.view.View
+import androidx.compose.runtime.getValue
+import com.android.compose.animation.scene.ContentKey
+import com.android.internal.jank.Cuj
+import com.android.internal.jank.Cuj.CujType
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
+import com.android.systemui.lifecycle.ExclusiveActivatable
+import com.android.systemui.lifecycle.Hydrator
+import com.android.systemui.scene.shared.model.Scenes
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+/**
+ * Monitors scene transitions and reports the beginning and ending of each scene-related CUJ.
+ *
+ * This general-purpose monitor can be expanded to include other rules that respond to the beginning
+ * and/or ending of transitions and reports jank CUI markers to the [InteractionJankMonitor].
+ */
+class SceneJankMonitor
+@AssistedInject
+constructor(
+ authenticationInteractor: AuthenticationInteractor,
+ private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
+ private val interactionJankMonitor: InteractionJankMonitor,
+) : ExclusiveActivatable() {
+
+ private val hydrator = Hydrator("SceneJankMonitor.hydrator")
+ private val authMethod: AuthenticationMethodModel? by
+ hydrator.hydratedStateOf(
+ traceName = "authMethod",
+ initialValue = null,
+ source = authenticationInteractor.authenticationMethod,
+ )
+
+ override suspend fun onActivated(): Nothing {
+ hydrator.activate()
+ }
+
+ /**
+ * Notifies that a transition is at its start.
+ *
+ * Should be called exactly once each time a new transition starts.
+ */
+ fun onTransitionStart(view: View, from: ContentKey, to: ContentKey, @CujType cuj: Int?) {
+ cuj.orCalculated(from, to) { nonNullCuj -> interactionJankMonitor.begin(view, nonNullCuj) }
+ }
+
+ /**
+ * Notifies that the previous transition is at its end.
+ *
+ * Should be called exactly once each time a transition ends.
+ */
+ fun onTransitionEnd(from: ContentKey, to: ContentKey, @CujType cuj: Int?) {
+ cuj.orCalculated(from, to) { nonNullCuj -> interactionJankMonitor.end(nonNullCuj) }
+ }
+
+ /**
+ * Returns this CUI marker (CUJ identifier), one that's calculated based on other state, or
+ * `null`, if no appropriate CUJ could be calculated.
+ */
+ private fun Int?.orCalculated(
+ from: ContentKey,
+ to: ContentKey,
+ ifNotNull: (nonNullCuj: Int) -> Unit,
+ ) {
+ val thisOrCalculatedCuj = this ?: calculatedCuj(from = from, to = to)
+
+ if (thisOrCalculatedCuj != null) {
+ ifNotNull(thisOrCalculatedCuj)
+ }
+ }
+
+ @CujType
+ private fun calculatedCuj(from: ContentKey, to: ContentKey): Int? {
+ val isDeviceUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked
+ return when {
+ to == Scenes.Bouncer ->
+ when (authMethod) {
+ is AuthenticationMethodModel.Pin,
+ is AuthenticationMethodModel.Sim -> Cuj.CUJ_LOCKSCREEN_PIN_APPEAR
+ is AuthenticationMethodModel.Pattern -> Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR
+ is AuthenticationMethodModel.Password -> Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR
+ is AuthenticationMethodModel.None -> null
+ null -> null
+ }
+ from == Scenes.Bouncer && isDeviceUnlocked ->
+ when (authMethod) {
+ is AuthenticationMethodModel.Pin,
+ is AuthenticationMethodModel.Sim -> Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR
+ is AuthenticationMethodModel.Pattern -> Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR
+ is AuthenticationMethodModel.Password -> Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR
+ is AuthenticationMethodModel.None -> null
+ null -> null
+ }
+ else -> null
+ }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): SceneJankMonitor
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index c45906840385..b8da2274eec1 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -34,6 +34,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
layoutInsetController: LayoutInsetsController,
sceneDataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
) {
setLayoutInsetsController(layoutInsetController)
SceneWindowRootViewBinder.bind(
@@ -52,6 +53,7 @@ class SceneWindowRootView(context: Context, attrs: AttributeSet?) : WindowRootVi
},
dataSourceDelegator = sceneDataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index f7061d9af961..7da007c2fe53 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -74,6 +74,7 @@ object SceneWindowRootViewBinder {
onVisibilityChangedInternal: (isVisible: Boolean) -> Unit,
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
) {
val unsortedSceneByKey: Map<SceneKey, Scene> = scenes.associateBy { scene -> scene.key }
val sortedSceneByKey: Map<SceneKey, Scene> =
@@ -133,6 +134,7 @@ object SceneWindowRootViewBinder {
dataSourceDelegator = dataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
containerConfig = containerConfig,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
.also { it.id = R.id.scene_container_root_composable }
)
@@ -169,6 +171,7 @@ object SceneWindowRootViewBinder {
dataSourceDelegator: SceneDataSourceDelegator,
qsSceneAdapter: Provider<QSSceneAdapter>,
containerConfig: SceneContainerConfig,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
): View {
return ComposeView(context).apply {
setContent {
@@ -185,6 +188,7 @@ object SceneWindowRootViewBinder {
sceneTransitions = containerConfig.transitions,
dataSourceDelegator = dataSourceDelegator,
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
index e5ff2529e335..7ec523bc4dc5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogDelegate.kt
@@ -19,6 +19,7 @@ import android.content.Context
import android.hardware.display.DisplayManager
import android.os.Bundle
import android.os.UserHandle
+import android.view.View
import androidx.annotation.StyleRes
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
@@ -103,7 +104,6 @@ class ScreenRecordPermissionDialogDelegate(
mediaProjectionMetricsLogger,
defaultSelectedMode,
displayManager,
- dialog,
controller,
activityStarter,
userContextProvider,
@@ -119,6 +119,12 @@ class ScreenRecordPermissionDialogDelegate(
super<BaseMediaProjectionPermissionDialogDelegate>.onCreate(dialog, savedInstanceState)
setDialogTitle(R.string.screenrecord_permission_dialog_title)
dialog.setTitle(R.string.screenrecord_title)
+ setStartButtonOnClickListener { v: View? ->
+ val screenRecordViewBinder: ScreenRecordPermissionViewBinder? =
+ viewBinder as ScreenRecordPermissionViewBinder?
+ screenRecordViewBinder?.startButtonOnClicked()
+ dialog.dismiss()
+ }
setCancelButtonOnClickListener { dialog.dismiss() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt
index 9f7e1ade964a..9fcb3dfc0ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionViewBinder.kt
@@ -18,7 +18,6 @@ package com.android.systemui.screenrecord
import android.annotation.SuppressLint
import android.app.Activity
-import android.app.AlertDialog
import android.app.PendingIntent
import android.content.Intent
import android.hardware.display.DisplayManager
@@ -57,7 +56,6 @@ class ScreenRecordPermissionViewBinder(
mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
@ScreenShareMode defaultSelectedMode: Int,
displayManager: DisplayManager,
- private val dialog: AlertDialog,
private val controller: RecordingController,
private val activityStarter: ActivityStarter,
private val userContextProvider: UserContextProvider,
@@ -69,56 +67,57 @@ class ScreenRecordPermissionViewBinder(
hostUid = hostUid,
mediaProjectionMetricsLogger,
defaultSelectedMode,
- dialog,
) {
private lateinit var tapsSwitch: Switch
private lateinit var audioSwitch: Switch
private lateinit var tapsView: View
private lateinit var options: Spinner
- override fun bind() {
- super.bind()
+ override fun bind(view: View) {
+ super.bind(view)
initRecordOptionsView()
- setStartButtonOnClickListener { _: View? ->
- onStartRecordingClicked?.run()
- if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
- requestScreenCapture(
- captureTarget = null,
- displayId = selectedScreenShareOption.displayId,
- )
- }
- if (selectedScreenShareOption.mode == SINGLE_APP) {
- val intent = Intent(dialog.context, MediaProjectionAppSelectorActivity::class.java)
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ setStartButtonOnClickListener { startButtonOnClicked() }
+ }
- // We can't start activity for result here so we use result receiver to get
- // the selected target to capture
- intent.putExtra(
- MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
- CaptureTargetResultReceiver(),
- )
+ fun startButtonOnClicked() {
+ onStartRecordingClicked?.run()
+ if (selectedScreenShareOption.mode == ENTIRE_SCREEN) {
+ requestScreenCapture(
+ captureTarget = null,
+ displayId = selectedScreenShareOption.displayId,
+ )
+ }
+ if (selectedScreenShareOption.mode == SINGLE_APP) {
+ val intent =
+ Intent(containerView.context, MediaProjectionAppSelectorActivity::class.java)
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- intent.putExtra(
- MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
- hostUserHandle,
- )
- intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
- intent.putExtra(
- MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE,
- MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name,
- )
- activityStarter.startActivity(intent, /* dismissShade= */ true)
- }
- dialog.dismiss()
+ // We can't start activity for result here so we use result receiver to get
+ // the selected target to capture
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_CAPTURE_REGION_RESULT_RECEIVER,
+ CaptureTargetResultReceiver(),
+ )
+
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_USER_HANDLE,
+ hostUserHandle,
+ )
+ intent.putExtra(MediaProjectionAppSelectorActivity.EXTRA_HOST_APP_UID, hostUid)
+ intent.putExtra(
+ MediaProjectionAppSelectorActivity.EXTRA_SCREEN_SHARE_TYPE,
+ MediaProjectionAppSelectorActivity.ScreenShareType.ScreenRecord.name,
+ )
+ activityStarter.startActivity(intent, /* dismissShade= */ true)
}
}
@SuppressLint("ClickableViewAccessibility")
private fun initRecordOptionsView() {
- audioSwitch = dialog.requireViewById(R.id.screenrecord_audio_switch)
- tapsSwitch = dialog.requireViewById(R.id.screenrecord_taps_switch)
+ audioSwitch = containerView.requireViewById(R.id.screenrecord_audio_switch)
+ tapsSwitch = containerView.requireViewById(R.id.screenrecord_taps_switch)
- tapsView = dialog.requireViewById(R.id.show_taps)
+ tapsView = containerView.requireViewById(R.id.show_taps)
updateTapsViewVisibility()
// Add these listeners so that the switch only responds to movement
@@ -126,10 +125,10 @@ class ScreenRecordPermissionViewBinder(
audioSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
tapsSwitch.setOnTouchListener { _, event -> event.action == ACTION_MOVE }
- options = dialog.requireViewById(R.id.screen_recording_options)
+ options = containerView.requireViewById(R.id.screen_recording_options)
val a: ArrayAdapter<*> =
ScreenRecordingAdapter(
- dialog.context,
+ containerView.context,
android.R.layout.simple_spinner_dropdown_item,
MODES,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index e168025b2bf8..19152170757c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -49,7 +49,6 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.content.ContentResolver;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Insets;
@@ -58,28 +57,23 @@ import android.graphics.Region;
import android.graphics.RenderEffect;
import android.graphics.Shader;
import android.os.Bundle;
-import android.os.Handler;
import android.os.Trace;
-import android.os.UserManager;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.MathUtils;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.AccessibilityDelegate;
import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
-import android.view.ViewStub;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.animation.Interpolator;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
@@ -105,14 +99,11 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInt
import com.android.systemui.doze.DozeLog;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver;
import com.android.systemui.keyguard.shared.model.ClockSize;
import com.android.systemui.keyguard.shared.model.Edge;
import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -162,7 +153,6 @@ import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
@@ -174,10 +164,8 @@ import com.android.systemui.statusbar.notification.stack.AnimationProperties;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor;
-import com.android.systemui.statusbar.phone.BounceInterpolator;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
@@ -254,7 +242,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* Whether the Shade should animate to reflect Back gesture progress.
* To minimize latency at runtime, we cache this, else we'd be reading it every time
* updateQsExpansion() is called... and it's called very often.
- *
+ * <p>
* Whenever we change this flag, SysUI is restarted, so it's never going to be "stale".
*/
@@ -285,8 +273,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final ConfigurationController mConfigurationController;
private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
- private final LayoutInflater mLayoutInflater;
- private final FeatureFlags mFeatureFlags;
private final AccessibilityManager mAccessibilityManager;
private final NotificationWakeUpCoordinator mWakeUpCoordinator;
private final PulseExpansionHandler mPulseExpansionHandler;
@@ -311,7 +297,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final DozeLog mDozeLog;
/** Whether or not the NotificationPanelView can be expanded or collapsed with a drag. */
private final boolean mNotificationsDragEnabled;
- private final Interpolator mBounceInterpolator;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ShadeExpansionStateManager mShadeExpansionStateManager;
private final ShadeRepository mShadeRepository;
@@ -321,7 +306,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final NotificationGutsManager mGutsManager;
private final AlternateBouncerInteractor mAlternateBouncerInteractor;
private final QuickSettingsControllerImpl mQsController;
- private final NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
private final TouchHandler mTouchHandler = new TouchHandler();
private long mDownTime;
@@ -436,7 +420,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
private final CommandQueue mCommandQueue;
- private final UserManager mUserManager;
private final MediaDataManager mMediaDataManager;
@PanelState
private int mCurrentPanelState = STATE_CLOSED;
@@ -462,7 +445,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private boolean mIsGestureNavigation;
private int mOldLayoutDirection;
- private final ContentResolver mContentResolver;
private float mMinFraction;
private final KeyguardMediaController mKeyguardMediaController;
@@ -475,7 +457,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private int mSplitShadeScrimTransitionDistance;
private final NotificationListContainer mNotificationListContainer;
- private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final NPVCDownEventState.Buffer mLastDownEvents;
private final KeyguardClockInteractor mKeyguardClockInteractor;
private float mMinExpandHeight;
@@ -530,8 +511,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private final KeyguardInteractor mKeyguardInteractor;
private final PowerInteractor mPowerInteractor;
private final CoroutineDispatcher mMainDispatcher;
- private boolean mIsAnyMultiShadeExpanded;
- private boolean mForceFlingAnimationForTest = false;
private final SplitShadeStateController mSplitShadeStateController;
private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -550,9 +529,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Inject
public NotificationPanelViewController(NotificationPanelView view,
- @Main Handler handler,
- @ShadeDisplayAware LayoutInflater layoutInflater,
- FeatureFlags featureFlags,
NotificationWakeUpCoordinator coordinator,
PulseExpansionHandler pulseExpansionHandler,
DynamicPrivacyController dynamicPrivacyController,
@@ -584,7 +560,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
ScrimController scrimController,
- UserManager userManager,
MediaDataManager mediaDataManager,
NotificationShadeDepthController notificationShadeDepthController,
AmbientState ambientState,
@@ -595,7 +570,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
QuickSettingsControllerImpl quickSettingsController,
FragmentService fragmentService,
IStatusBarService statusBarService,
- ContentResolver contentResolver,
ShadeHeaderController shadeHeaderController,
ScreenOffAnimationController screenOffAnimationController,
LockscreenGestureLogger lockscreenGestureLogger,
@@ -606,7 +580,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
KeyguardIndicationController keyguardIndicationController,
NotificationListContainer notificationListContainer,
- NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
SystemClock systemClock,
KeyguardClockInteractor keyguardClockInteractor,
@@ -625,7 +598,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
SplitShadeStateController splitShadeStateController,
PowerInteractor powerInteractor,
KeyguardClockPositionAlgorithm keyguardClockPositionAlgorithm,
- NaturalScrollingSettingObserver naturalScrollingSettingObserver,
MSDLPlayer msdlPlayer,
BrightnessMirrorShowingInteractor brightnessMirrorShowingInteractor) {
SceneContainerFlag.assertInLegacyMode();
@@ -651,7 +623,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mKeyguardInteractor = keyguardInteractor;
mPowerInteractor = powerInteractor;
mClockPositionAlgorithm = keyguardClockPositionAlgorithm;
- mNaturalScrollingSettingObserver = naturalScrollingSettingObserver;
mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -691,7 +662,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
.setY2(0.84f)
.build();
mLatencyTracker = latencyTracker;
- mBounceInterpolator = new BounceInterpolator();
mFalsingManager = falsingManager;
mDozeLog = dozeLog;
mNotificationsDragEnabled = mResources.getBoolean(
@@ -708,13 +678,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mMediaHierarchyManager = mediaHierarchyManager;
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
- mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mNavigationBarController = navigationBarController;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
mDepthController = notificationShadeDepthController;
- mContentResolver = contentResolver;
mFragmentService = fragmentService;
mStatusBarService = statusBarService;
mSplitShadeStateController = splitShadeStateController;
@@ -722,8 +690,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
mView.setWillNotDraw(!DEBUG_DRAWABLE);
mShadeHeaderController = shadeHeaderController;
- mLayoutInflater = layoutInflater;
- mFeatureFlags = featureFlags;
mAnimateBack = predictiveBackAnimateShade();
mFalsingCollector = falsingCollector;
mWakeUpCoordinator = coordinator;
@@ -736,7 +702,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mPulseExpansionHandler = pulseExpansionHandler;
mDozeParameters = dozeParameters;
mScrimController = scrimController;
- mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
mSysUiState = sysUiState;
@@ -889,7 +854,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// Dreaming->Lockscreen
collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
- setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+ setDreamLockscreenTransitionAlpha(),
mMainDispatcher);
collectFlow(mView, mKeyguardTransitionInteractor.transition(
@@ -963,28 +928,31 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void updateResources() {
- Trace.beginSection("NSSLC#updateResources");
- final boolean newSplitShadeEnabled =
- mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
- final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
- mSplitShadeEnabled = newSplitShadeEnabled;
- mQsController.updateResources();
- mNotificationsQSContainerController.updateResources();
- updateKeyguardStatusViewAlignment(/* animate= */false);
- mKeyguardMediaController.refreshMediaPosition(
- "NotificationPanelViewController.updateResources");
-
- if (splitShadeChanged) {
- if (isPanelVisibleBecauseOfHeadsUp()) {
- // workaround for b/324642496, because HUNs set state to OPENING
- onPanelStateChanged(STATE_CLOSED);
+ try {
+ Trace.beginSection("NSSLC#updateResources");
+ final boolean newSplitShadeEnabled =
+ mSplitShadeStateController.shouldUseSplitNotificationShade(mResources);
+ final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
+ mSplitShadeEnabled = newSplitShadeEnabled;
+ mQsController.updateResources();
+ mNotificationsQSContainerController.updateResources();
+ updateKeyguardStatusViewAlignment();
+ mKeyguardMediaController.refreshMediaPosition(
+ "NotificationPanelViewController.updateResources");
+
+ if (splitShadeChanged) {
+ if (isPanelVisibleBecauseOfHeadsUp()) {
+ // workaround for b/324642496, because HUNs set state to OPENING
+ onPanelStateChanged(STATE_CLOSED);
+ }
+ onSplitShadeEnabledChanged();
}
- onSplitShadeEnabledChanged();
- }
- mSplitShadeFullTransitionDistance =
- mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
- Trace.endSection();
+ mSplitShadeFullTransitionDistance =
+ mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+ } finally {
+ Trace.endSection();
+ }
}
private void onSplitShadeEnabledChanged() {
@@ -1011,29 +979,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mQsController.updateQsState();
}
- private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
- View view = mView.findViewById(viewId);
- if (view != null) {
- int index = mView.indexOfChild(view);
- mView.removeView(view);
- if (enabled) {
- view = mLayoutInflater.inflate(layoutId, mView, false);
- mView.addView(view, index);
- } else {
- // Add the stub back so we can re-inflate it again if necessary
- ViewStub stub = new ViewStub(mView.getContext(), layoutId);
- stub.setId(stubId);
- mView.addView(stub, index);
- view = null;
- }
- } else if (enabled) {
- // It's possible the stub was never inflated if the configuration changed
- ViewStub stub = mView.findViewById(stubId);
- view = stub.inflate();
- }
- return view;
- }
-
@VisibleForTesting
void reInflateViews() {
debugLog("reInflateViews");
@@ -1042,11 +987,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mStatusBarStateController.getInterpolatedDozeAmount());
}
- @VisibleForTesting
- boolean isFlinging() {
- return mIsFlinging;
- }
-
/** Sets a listener to be notified when the shade starts opening or finishes closing. */
public void setOpenCloseListener(OpenCloseListener openCloseListener) {
SceneContainerFlag.assertInLegacyMode();
@@ -1096,8 +1036,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* @param forceClockUpdate Should the clock be updated even when not on keyguard
*/
private void positionClockAndNotifications(boolean forceClockUpdate) {
- boolean animate = !SceneContainerFlag.isEnabled()
- && mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
int stackScrollerPadding;
boolean onKeyguard = isKeyguardShowing();
@@ -1120,14 +1058,14 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mNotificationStackScrollLayoutController.setIntrinsicPadding(stackScrollerPadding);
mStackScrollerMeasuringPass++;
- requestScrollerTopPaddingUpdate(animate);
+ requestScrollerTopPaddingUpdate();
mStackScrollerMeasuringPass = 0;
mAnimateNextPositionUpdate = false;
}
private void updateClockAppearance() {
mKeyguardClockInteractor.setClockSize(computeDesiredClockSize());
- updateKeyguardStatusViewAlignment(/* animate= */true);
+ updateKeyguardStatusViewAlignment();
float darkAmount =
mScreenOffAnimationController.shouldExpandNotifications()
@@ -1146,10 +1084,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private ClockSize computeDesiredClockSize() {
- if (shouldForceSmallClock()) {
- return ClockSize.SMALL;
- }
-
if (mSplitShadeEnabled) {
return computeDesiredClockSizeForSplitShade();
}
@@ -1174,17 +1108,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return ClockSize.LARGE;
}
- private boolean shouldForceSmallClock() {
- return mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)
- && !isOnAod()
- // True on small landscape screens
- && mResources.getBoolean(R.bool.force_small_clock_on_lockscreen);
- }
-
- private void updateKeyguardStatusViewAlignment(boolean animate) {
+ private void updateKeyguardStatusViewAlignment() {
boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
- mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
}
private boolean shouldKeyguardStatusViewBeCentered() {
@@ -1214,14 +1140,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
private boolean hasVisibleNotifications() {
- if (FooterViewRefactor.isEnabled()) {
- return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
- || mMediaDataManager.hasActiveMediaOrRecommendation();
- } else {
- return mNotificationStackScrollLayoutController
- .getVisibleNotificationCount() != 0
- || mMediaDataManager.hasActiveMediaOrRecommendation();
- }
+ return mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
+ || mMediaDataManager.hasActiveMediaOrRecommendation();
}
@Override
@@ -1464,7 +1384,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
});
- if (!mScrimController.isScreenOn() && !mForceFlingAnimationForTest) {
+ if (!mScrimController.isScreenOn()) {
animator.setDuration(1);
}
setAnimator(animator);
@@ -1472,16 +1392,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
@VisibleForTesting
- void setForceFlingAnimationForTest(boolean force) {
- mForceFlingAnimationForTest = force;
- }
-
- @VisibleForTesting
void onFlingEnd(boolean cancelled) {
mIsFlinging = false;
mExpectingSynthesizedDown = false;
// No overshoot when the animation ends
- setOverExpansionInternal(0, false /* isFromGesture */);
+ setOverExpansionInternal(0);
setAnimator(null);
mKeyguardStateController.notifyPanelFlingEnd();
if (!cancelled) {
@@ -1572,7 +1487,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
/** Return whether a touch is near the gesture handle at the bottom of screen */
- boolean isInGestureNavHomeHandleArea(float x, float y) {
+ boolean isInGestureNavHomeHandleArea(float y) {
return mIsGestureNavigation && y > mView.getHeight() - mNavigationBarBottomHeight;
}
@@ -1605,7 +1520,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* There are two scenarios behind this function call. First, input focus transfer has
* successfully happened and this view already received synthetic DOWN event.
* (mExpectingSynthesizedDown == false). Do nothing.
- *
+ * <p>
* Second, before input focus transfer finished, user may have lifted finger in previous window
* and this window never received synthetic DOWN event. (mExpectingSynthesizedDown == true). In
* this case, we use the velocity to trigger fling event.
@@ -1766,7 +1681,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return mBarState == KEYGUARD;
}
- void requestScrollerTopPaddingUpdate(boolean animate) {
+ void requestScrollerTopPaddingUpdate() {
if (!SceneContainerFlag.isEnabled()) {
float padding = mQsController.calculateNotificationsTopPadding(mIsExpandingOrCollapsing,
getKeyguardNotificationStaticPadding(), mExpandedFraction);
@@ -2041,11 +1956,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
@VisibleForTesting
- void setTouchSlopExceeded(boolean isTouchSlopExceeded) {
- mTouchSlopExceeded = isTouchSlopExceeded;
- }
-
- @VisibleForTesting
void setOverExpansion(float overExpansion) {
if (overExpansion == mOverExpansion) {
return;
@@ -2218,9 +2128,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void setBouncerShowing(boolean bouncerShowing) {
mBouncerShowing = bouncerShowing;
- if (!FooterViewRefactor.isEnabled()) {
- mNotificationStackScrollLayoutController.updateShowEmptyShadeView();
- }
updateVisibility();
}
@@ -2397,7 +2304,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
final float dozeAmount = dozing ? 1 : 0;
mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
- updateKeyguardStatusViewAlignment(animate);
+ updateKeyguardStatusViewAlignment();
}
@Override
@@ -2416,7 +2323,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
mNotificationStackScrollLayoutController.setPulsing(pulsing, animatePulse);
- updateKeyguardStatusViewAlignment(/* animate= */ true);
+ updateKeyguardStatusViewAlignment();
}
public void performHapticFeedback(int constant) {
@@ -2982,8 +2889,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mIsSpringBackAnimation = true;
ValueAnimator animator = ValueAnimator.ofFloat(mOverExpansion, 0);
animator.addUpdateListener(
- animation -> setOverExpansionInternal((float) animation.getAnimatedValue(),
- false /* isFromGesture */));
+ animation -> setOverExpansionInternal((float) animation.getAnimatedValue()));
animator.setDuration(SHADE_OPEN_SPRING_BACK_DURATION);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addListener(new AnimatorListenerAdapter() {
@@ -3075,19 +2981,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
* Set the current overexpansion
*
* @param overExpansion the amount of overexpansion to apply
- * @param isFromGesture is this amount from a gesture and needs to be rubberBanded?
*/
- private void setOverExpansionInternal(float overExpansion, boolean isFromGesture) {
- if (!isFromGesture) {
- mLastGesturedOverExpansion = -1;
- setOverExpansion(overExpansion);
- } else if (mLastGesturedOverExpansion != overExpansion) {
- mLastGesturedOverExpansion = overExpansion;
- final float heightForFullOvershoot = mView.getHeight() / 3.0f;
- float newExpansion = MathUtils.saturate(overExpansion / heightForFullOvershoot);
- newExpansion = Interpolators.getOvershootInterpolation(newExpansion);
- setOverExpansion(newExpansion * mPanelFlingOvershootAmount * 2.0f);
- }
+ private void setOverExpansionInternal(float overExpansion) {
+ mLastGesturedOverExpansion = -1;
+ setOverExpansion(overExpansion);
}
/** Sets the expanded height relative to a number from 0 to 1. */
@@ -3183,29 +3080,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
/**
- * Phase 2: Bounce down.
- */
- private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
- ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
- animator.setDuration(450);
- animator.setInterpolator(mBounceInterpolator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setAnimator(null);
- onAnimationFinished.run();
- updateExpansionAndVisibility();
- }
- });
- animator.start();
- setAnimator(animator);
- }
-
- private ValueAnimator createHeightAnimator(float targetHeight) {
- return createHeightAnimator(targetHeight, 0.0f /* performOvershoot */);
- }
-
- /**
* Create an animator that can also overshoot
*
* @param targetHeight the target height
@@ -3225,7 +3099,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mPanelFlingOvershootAmount * overshootAmount,
Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
animator.getAnimatedFraction()));
- setOverExpansionInternal(expansion, false /* isFromGesture */);
+ setOverExpansionInternal(expansion);
}
setExpandedHeightInternal((float) animation.getAnimatedValue());
});
@@ -3280,8 +3154,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mFixedDuration = NO_FIXED_DURATION;
}
- boolean postToView(Runnable action) {
- return mView.post(action);
+ void postToView(Runnable action) {
+ mView.post(action);
}
/** Sends an external (e.g. Status Bar) intercept touch event to the Shade touch handler. */
@@ -3360,7 +3234,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
return mShadeExpansionStateManager;
}
- void onQsExpansionChanged(boolean expanded) {
+ void onQsExpansionChanged() {
updateExpandedHeightToMaxHeight();
updateSystemUiStateFlags();
NavigationBarView navigationBarView =
@@ -3372,7 +3246,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@VisibleForTesting
void onQsSetExpansionHeightCalled(boolean qsFullyExpanded) {
- requestScrollerTopPaddingUpdate(false);
+ requestScrollerTopPaddingUpdate();
mKeyguardStatusBarViewController.updateViewState();
int barState = getBarState();
if (barState == SHADE_LOCKED || barState == KEYGUARD) {
@@ -3413,7 +3287,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void onExpansionHeightSetToMax(boolean requestPaddingUpdate) {
if (requestPaddingUpdate) {
- requestScrollerTopPaddingUpdate(false /* animate */);
+ requestScrollerTopPaddingUpdate();
}
updateExpandedHeightToMaxHeight();
}
@@ -3437,7 +3311,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
? (ExpandableNotificationRow) firstChildNotGone : null;
if (firstRow != null && (view == firstRow || (firstRow.getNotificationParent()
== firstRow))) {
- requestScrollerTopPaddingUpdate(false /* animate */);
+ requestScrollerTopPaddingUpdate();
}
updateExpandedHeightToMaxHeight();
}
@@ -3517,7 +3391,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
boolean animatingUnlockedShadeToKeyguardBypass
) {
boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
- boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
int oldState = mBarState;
boolean keyguardShowing = statusBarState == KEYGUARD;
@@ -3738,17 +3611,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
if (state == STATE_CLOSED) {
mQsController.setExpandImmediate(false);
- // Close the status bar in the next frame so we can show the end of the
- // animation.
- if (!mIsAnyMultiShadeExpanded) {
- mView.post(mMaybeHideExpandedRunnable);
- }
+ // Close the status bar in the next frame so we can show the end of the animation.
+ mView.post(mMaybeHideExpandedRunnable);
}
mCurrentPanelState = state;
}
- private Consumer<Float> setDreamLockscreenTransitionAlpha(
- NotificationStackScrollLayoutController stackScroller) {
+ private Consumer<Float> setDreamLockscreenTransitionAlpha() {
return (Float alpha) -> {
// Also animate the status bar's alpha during transitions between the lockscreen and
// dreams.
@@ -4285,4 +4154,3 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index c88e7b827881..d05837261b89 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -86,7 +86,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -96,8 +95,8 @@ import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.ShadeTouchableRegionManager;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.CastController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
@@ -115,9 +114,7 @@ import java.io.PrintWriter;
import javax.inject.Inject;
import javax.inject.Provider;
-/** Handles QuickSettings touch handling, expansion and animation state
- * TODO (b/264460656) make this dumpable
- */
+/** Handles QuickSettings touch handling, expansion and animation state. */
@SysUISingleton
public class QuickSettingsControllerImpl implements QuickSettingsController, Dumpable {
public static final String TAG = "QuickSettingsController";
@@ -295,7 +292,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
private ValueAnimator mSizeChangeAnimator;
private ExpansionHeightListener mExpansionHeightListener;
- private QsStateUpdateListener mQsStateUpdateListener;
private ApplyClippingImmediatelyListener mApplyClippingImmediatelyListener;
private FlingQsWithoutClickListener mFlingQsWithoutClickListener;
private ExpansionHeightSetToMaxListener mExpansionHeightSetToMaxListener;
@@ -402,10 +398,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
mExpansionHeightListener = listener;
}
- void setQsStateUpdateListener(QsStateUpdateListener listener) {
- mQsStateUpdateListener = listener;
- }
-
void setApplyClippingImmediatelyListener(ApplyClippingImmediatelyListener listener) {
mApplyClippingImmediatelyListener = listener;
}
@@ -563,7 +555,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
}
// TODO (b/265193930): remove dependency on NPVC
// Let's reject anything at the very bottom around the home handle in gesture nav
- if (mPanelViewControllerLazy.get().isInGestureNavHomeHandleArea(x, y)) {
+ if (mPanelViewControllerLazy.get().isInGestureNavHomeHandleArea(y)) {
return false;
}
return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
@@ -805,7 +797,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
if (changed) {
mShadeRepository.setLegacyIsQsExpanded(expanded);
updateQsState();
- mPanelViewControllerLazy.get().onQsExpansionChanged(expanded);
+ mPanelViewControllerLazy.get().onQsExpansionChanged();
mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
getMinExpansionHeight(), getMaxExpansionHeight(),
mStackScrollerOverscrolling, mAnimatorExpand, mAnimating);
@@ -1022,16 +1014,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
}
void updateQsState() {
- if (!FooterViewRefactor.isEnabled()) {
- // Update full screen state; note that this will be true if the QS panel is only
- // partially expanded, and that is fixed with the footer view refactor.
- setQsFullScreen(/* qsFullScreen = */ getExpanded() && !mSplitShadeEnabled);
- }
-
- if (mQsStateUpdateListener != null) {
- mQsStateUpdateListener.onQsStateUpdated(getExpanded(), mStackScrollerOverscrolling);
- }
-
if (mQs == null) return;
mQs.setExpanded(getExpanded());
}
@@ -1094,10 +1076,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
// Update the light bar
mLightBarController.setQsExpanded(mFullyExpanded);
- if (FooterViewRefactor.isEnabled()) {
- // Update full screen state
- setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled);
- }
+ // Update full screen state
+ setQsFullScreen(/* qsFullScreen = */ mFullyExpanded && !mSplitShadeEnabled);
}
float getLockscreenShadeDragProgress() {
@@ -1212,7 +1192,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
/**
* Applies clipping to quick settings, notifications layout and
* updates bounds of the notifications background (notifications scrim).
- *
+ * <p>
* The parameters are bounds of the notifications area rectangle, this function
* calculates bounds for the QS clipping based on the notifications bounds.
*/
@@ -2268,10 +2248,8 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
setExpansionHeight(qsHeight);
}
- boolean hasNotifications = FooterViewRefactor.isEnabled()
- ? mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()
- : mNotificationStackScrollLayoutController.getVisibleNotificationCount()
- != 0;
+ boolean hasNotifications =
+ mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue();
if (!hasNotifications && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
// No notifications are visible, let's animate to the height of qs instead
if (isQsFragmentCreated()) {
@@ -2406,10 +2384,6 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum
void onQsSetExpansionHeightCalled(boolean qsFullyExpanded);
}
- interface QsStateUpdateListener {
- void onQsStateUpdated(boolean qsExpanded, boolean isStackScrollerOverscrolling);
- }
-
interface ApplyClippingImmediatelyListener {
void onQsClippingImmediatelyApplied(boolean clipStatusView, Rect lastQsClipBounds,
int top, boolean qsFragmentCreated, boolean qsVisible);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index e19112047d2a..3449e81a4630 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -41,6 +41,7 @@ import com.android.systemui.scene.shared.model.SceneContainerConfig
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.scene.ui.composable.Scene
+import com.android.systemui.scene.ui.view.SceneJankMonitor
import com.android.systemui.scene.ui.view.SceneWindowRootView
import com.android.systemui.scene.ui.view.WindowRootView
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
@@ -89,6 +90,7 @@ abstract class ShadeViewProviderModule {
layoutInsetController: NotificationInsetsController,
sceneDataSourceDelegator: Provider<SceneDataSourceDelegator>,
qsSceneAdapter: Provider<QSSceneAdapter>,
+ sceneJankMonitorFactory: SceneJankMonitor.Factory,
): WindowRootView {
return if (SceneContainerFlag.isEnabled) {
checkNoSceneDuplicates(scenesProvider.get())
@@ -104,6 +106,7 @@ abstract class ShadeViewProviderModule {
layoutInsetController = layoutInsetController,
sceneDataSourceDelegator = sceneDataSourceDelegator.get(),
qsSceneAdapter = qsSceneAdapter,
+ sceneJankMonitorFactory = sceneJankMonitorFactory,
)
sceneWindowRootView
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt
index a747abbc6a6e..1c14d3349027 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/ChronometerText.kt
@@ -28,17 +28,11 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.Measurable
-import androidx.compose.ui.layout.MeasureResult
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.node.LayoutModifierNode
-import androidx.compose.ui.node.ModifierNodeElement
import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.constrain
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.statusbar.chips.ui.compose.modifiers.neverDecreaseWidth
import kotlinx.coroutines.delay
/** Platform-optimized interface for getting current time */
@@ -97,35 +91,3 @@ fun ChronometerText(
modifier = modifier.neverDecreaseWidth(),
)
}
-
-/** A modifier that ensures the width of the content only increases and never decreases. */
-private fun Modifier.neverDecreaseWidth(): Modifier {
- return this.then(neverDecreaseWidthElement)
-}
-
-private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() {
- override fun create(): NeverDecreaseWidthNode {
- return NeverDecreaseWidthNode()
- }
-
- override fun update(node: NeverDecreaseWidthNode) {
- error("This should never be called")
- }
-}
-
-private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode {
- private var minWidth = 0
-
- override fun MeasureScope.measure(
- measurable: Measurable,
- constraints: Constraints,
- ): MeasureResult {
- val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints))
- val width = placeable.width
- val height = placeable.height
-
- minWidth = maxOf(minWidth, width)
-
- return layout(width, height) { placeable.place(0, 0) }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt
new file mode 100644
index 000000000000..505a5fcb18b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/modifiers/NeverDecreaseWidth.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.chips.ui.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.constrain
+
+/** A modifier that ensures the width of the content only increases and never decreases. */
+fun Modifier.neverDecreaseWidth(): Modifier {
+ return this.then(neverDecreaseWidthElement)
+}
+
+private data object neverDecreaseWidthElement : ModifierNodeElement<NeverDecreaseWidthNode>() {
+ override fun create(): NeverDecreaseWidthNode {
+ return NeverDecreaseWidthNode()
+ }
+
+ override fun update(node: NeverDecreaseWidthNode) {
+ error("This should never be called")
+ }
+}
+
+private class NeverDecreaseWidthNode : Modifier.Node(), LayoutModifierNode {
+ private var minWidth = 0
+
+ override fun MeasureScope.measure(
+ measurable: Measurable,
+ constraints: Constraints,
+ ): MeasureResult {
+ val placeable = measurable.measure(Constraints(minWidth = minWidth).constrain(constraints))
+ val width = placeable.width
+ val height = placeable.height
+
+ minWidth = maxOf(minWidth, width)
+
+ return layout(width, height) { placeable.place(0, 0) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 254b792f8152..d327fc23fd06 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.dagger;
-import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
-
import android.content.Context;
import android.os.Handler;
import android.os.RemoteException;
@@ -28,7 +26,6 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.CoreStartable;
import com.android.systemui.animation.ActivityTransitionAnimator;
-import com.android.systemui.animation.AnimationFeatureFlags;
import com.android.systemui.animation.DialogTransitionAnimator;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.dagger.SysUISingleton;
@@ -226,8 +223,7 @@ public interface CentralSurfacesDependenciesModule {
IDreamManager dreamManager,
KeyguardStateController keyguardStateController,
Lazy<AlternateBouncerInteractor> alternateBouncerInteractor,
- InteractionJankMonitor interactionJankMonitor,
- AnimationFeatureFlags animationFeatureFlags) {
+ InteractionJankMonitor interactionJankMonitor) {
DialogTransitionAnimator.Callback callback = new DialogTransitionAnimator.Callback() {
@Override
public boolean isDreaming() {
@@ -249,19 +245,6 @@ public interface CentralSurfacesDependenciesModule {
return alternateBouncerInteractor.get().canShowAlternateBouncerForFingerprint();
}
};
- return new DialogTransitionAnimator(
- mainExecutor, callback, interactionJankMonitor, animationFeatureFlags);
- }
-
- /** */
- @Provides
- @SysUISingleton
- static AnimationFeatureFlags provideAnimationFeatureFlags() {
- return new AnimationFeatureFlags() {
- @Override
- public boolean isPredictiveBackQsDialogAnim() {
- return predictiveBackAnimateDialogs();
- }
- };
+ return new DialogTransitionAnimator(mainExecutor, callback, interactionJankMonitor);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
index 4e68bee295fc..e3e77e16be6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/domain/interactor/MediaControlChipInteractor.kt
@@ -21,6 +21,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.media.controls.data.repository.MediaFilterRepository
import com.android.systemui.media.controls.shared.model.MediaCommonModel
import com.android.systemui.media.controls.shared.model.MediaData
+import com.android.systemui.res.R
import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -71,5 +72,10 @@ constructor(
}
private fun MediaData.toMediaControlChipModel(): MediaControlChipModel {
- return MediaControlChipModel(appIcon = this.appIcon, appName = this.app, songName = this.song)
+ return MediaControlChipModel(
+ appIcon = this.appIcon,
+ appName = this.app,
+ songName = this.song,
+ playOrPause = this.semanticActions?.getActionById(R.id.actionPlayPause),
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/shared/model/MediaControlChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/shared/model/MediaControlChipModel.kt
index 403566749e03..2e47c1eb9eca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/shared/model/MediaControlChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/shared/model/MediaControlChipModel.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.featurepods.media.shared.model
import android.graphics.drawable.Icon
+import com.android.systemui.media.controls.shared.model.MediaAction
/** Model used to display a media control chip in the status bar. */
data class MediaControlChipModel(
val appIcon: Icon?,
val appName: String?,
val songName: CharSequence?,
+ val playOrPause: MediaAction?,
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
index 2aea7d85e01a..19acb2e9839c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/media/ui/viewmodel/MediaControlChipViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.statusbar.featurepods.media.domain.interactor.MediaControlChipInteractor
import com.android.systemui.statusbar.featurepods.media.shared.model.MediaControlChipModel
+import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipId
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
import com.android.systemui.statusbar.featurepods.popups.ui.viewmodel.StatusBarPopupChipViewModel
@@ -33,6 +34,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
/**
* [StatusBarPopupChipViewModel] for a media control chip in the status bar. This view model is
@@ -54,40 +56,51 @@ constructor(
*/
override val chip: StateFlow<PopupChipModel> =
mediaControlChipInteractor.mediaControlModel
- .map { mediaControlModel -> toPopupChipModel(mediaControlModel, applicationContext) }
+ .map { mediaControlModel -> toPopupChipModel(mediaControlModel) }
.stateIn(
backgroundScope,
SharingStarted.WhileSubscribed(),
PopupChipModel.Hidden(PopupChipId.MediaControl),
)
-}
-private fun toPopupChipModel(model: MediaControlChipModel?, context: Context): PopupChipModel {
- if (model == null || model.songName.isNullOrEmpty()) {
- return PopupChipModel.Hidden(PopupChipId.MediaControl)
- }
+ private fun toPopupChipModel(model: MediaControlChipModel?): PopupChipModel {
+ if (model == null || model.songName.isNullOrEmpty()) {
+ return PopupChipModel.Hidden(PopupChipId.MediaControl)
+ }
- val contentDescription = model.appName?.let { ContentDescription.Loaded(description = it) }
- return PopupChipModel.Shown(
- chipId = PopupChipId.MediaControl,
- icon =
- model.appIcon?.loadDrawable(context)?.let {
+ val contentDescription = model.appName?.let { ContentDescription.Loaded(description = it) }
+
+ val defaultIcon =
+ model.appIcon?.loadDrawable(applicationContext)?.let {
Icon.Loaded(drawable = it, contentDescription = contentDescription)
}
?: Icon.Resource(
res = com.android.internal.R.drawable.ic_audio_media,
contentDescription = contentDescription,
- ),
- hoverIcon =
- Icon.Resource(
- res = com.android.internal.R.drawable.ic_media_pause,
- contentDescription = null,
- ),
- chipText = model.songName.toString(),
- isToggled = false,
- // TODO(b/385202114): Show a popup containing the media carousal when the chip is toggled.
- onToggle = {},
- // TODO(b/385202193): Add support for clicking on the icon on a media chip.
- onIconPressed = {},
- )
+ )
+ return PopupChipModel.Shown(
+ chipId = PopupChipId.MediaControl,
+ icon = defaultIcon,
+ chipText = model.songName.toString(),
+ isToggled = false,
+ // TODO(b/385202114): Show a popup containing the media carousal when the chip is
+ // toggled.
+ onToggle = {},
+ hoverBehavior = createHoverBehavior(model),
+ )
+ }
+
+ private fun createHoverBehavior(model: MediaControlChipModel): HoverBehavior {
+ val playOrPause = model.playOrPause ?: return HoverBehavior.None
+ val icon = playOrPause.icon ?: return HoverBehavior.None
+ val action = playOrPause.action ?: return HoverBehavior.None
+
+ val contentDescription =
+ ContentDescription.Loaded(description = playOrPause.contentDescription.toString())
+
+ return HoverBehavior.Button(
+ icon = Icon.Loaded(drawable = icon, contentDescription = contentDescription),
+ onIconPressed = { backgroundScope.launch { action.run() } },
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
index e7e3d02ae4c5..683b97166f3e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/shared/model/PopupChipModel.kt
@@ -26,6 +26,18 @@ sealed class PopupChipId(val value: String) {
data object MediaControl : PopupChipId("MediaControl")
}
+/** Defines the behavior of the chip when hovered over. */
+sealed interface HoverBehavior {
+ /** No specific hover behavior. The default icon will be shown. */
+ data object None : HoverBehavior
+
+ /**
+ * Shows a button on hover with the given [icon] and executes [onIconPressed] when the icon is
+ * pressed.
+ */
+ data class Button(val icon: Icon, val onIconPressed: () -> Unit) : HoverBehavior
+}
+
/** Model for individual status bar popup chips. */
sealed class PopupChipModel {
abstract val logName: String
@@ -40,15 +52,10 @@ sealed class PopupChipModel {
override val chipId: PopupChipId,
/** Default icon displayed on the chip */
val icon: Icon,
- /**
- * Icon to be displayed if the chip is hovered. i.e. the mouse pointer is inside the bounds
- * of the chip.
- */
- val hoverIcon: Icon,
val chipText: String,
val isToggled: Boolean = false,
val onToggle: () -> Unit,
- val onIconPressed: () -> Unit,
+ val hoverBehavior: HoverBehavior = HoverBehavior.None,
) : PopupChipModel() {
override val logName = "Shown(id=$chipId, toggled=$isToggled)"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
index 1a775d71983c..34bef9d3ca3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/featurepods/popups/ui/compose/StatusBarPopupChip.kt
@@ -25,7 +25,6 @@ import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -42,7 +41,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
+import com.android.compose.modifiers.thenIf
import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.statusbar.featurepods.popups.shared.model.HoverBehavior
import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipModel
/**
@@ -52,52 +53,59 @@ import com.android.systemui.statusbar.featurepods.popups.shared.model.PopupChipM
*/
@Composable
fun StatusBarPopupChip(model: PopupChipModel.Shown, modifier: Modifier = Modifier) {
- val interactionSource = remember { MutableInteractionSource() }
- val isHovered by interactionSource.collectIsHoveredAsState()
+ val hasHoverBehavior = model.hoverBehavior !is HoverBehavior.None
+ val hoverInteractionSource = remember { MutableInteractionSource() }
+ val isHovered by hoverInteractionSource.collectIsHoveredAsState()
val isToggled = model.isToggled
+ val chipBackgroundColor =
+ if (isToggled) {
+ MaterialTheme.colorScheme.primaryContainer
+ } else {
+ MaterialTheme.colorScheme.surfaceContainerHighest
+ }
Surface(
shape = RoundedCornerShape(16.dp),
modifier =
modifier
- .hoverable(interactionSource = interactionSource)
- .padding(vertical = 4.dp)
.widthIn(max = 120.dp)
+ .padding(vertical = 4.dp)
.animateContentSize()
- .clickable(onClick = { model.onToggle() }),
- color =
- if (isToggled) {
- MaterialTheme.colorScheme.primaryContainer
- } else {
- MaterialTheme.colorScheme.surfaceContainerHighest
- },
+ .thenIf(hasHoverBehavior) { Modifier.hoverable(hoverInteractionSource) }
+ .clickable { model.onToggle() },
+ color = chipBackgroundColor,
) {
Row(
modifier = Modifier.padding(start = 4.dp, end = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
- val currentIcon = if (isHovered) model.hoverIcon else model.icon
- val backgroundColor =
- if (isToggled) {
- MaterialTheme.colorScheme.primary
- } else {
- MaterialTheme.colorScheme.primaryContainer
- }
-
+ val iconColor =
+ if (isHovered) chipBackgroundColor else contentColorFor(chipBackgroundColor)
+ val hoverBehavior = model.hoverBehavior
+ val iconBackgroundColor = contentColorFor(chipBackgroundColor)
+ val iconInteractionSource = remember { MutableInteractionSource() }
Icon(
- icon = currentIcon,
+ icon =
+ when {
+ isHovered && hoverBehavior is HoverBehavior.Button -> hoverBehavior.icon
+ else -> model.icon
+ },
modifier =
- Modifier.background(color = backgroundColor, shape = CircleShape)
- .clickable(
- role = Role.Button,
- onClick = model.onIconPressed,
- indication = ripple(),
- interactionSource = remember { MutableInteractionSource() },
- )
- .padding(2.dp)
- .size(18.dp),
- tint = contentColorFor(backgroundColor),
+ Modifier.thenIf(isHovered) {
+ Modifier.padding(3.dp)
+ .background(color = iconBackgroundColor, shape = CircleShape)
+ }
+ .thenIf(hoverBehavior is HoverBehavior.Button) {
+ Modifier.clickable(
+ role = Role.Button,
+ onClick = (hoverBehavior as HoverBehavior.Button).onIconPressed,
+ indication = ripple(),
+ interactionSource = iconInteractionSource,
+ )
+ }
+ .padding(3.dp),
+ tint = iconColor,
)
Text(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
index 32de65be5b5b..d4d3cdf42fb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt
@@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
import javax.inject.Inject
@@ -43,7 +42,8 @@ internal constructor(
private val groupExpansionManagerImpl: GroupExpansionManagerImpl,
private val renderListInteractor: RenderNotificationListInteractor,
private val activeNotificationsInteractor: ActiveNotificationsInteractor,
- private val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
+ private val sensitiveNotificationProtectionController:
+ SensitiveNotificationProtectionController,
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
@@ -51,14 +51,11 @@ internal constructor(
groupExpansionManagerImpl.attach(pipeline)
}
+ // TODO: b/293167744 - Remove controller param.
private fun onAfterRenderList(entries: List<ListEntry>, controller: NotifStackController) =
traceSection("StackCoordinator.onAfterRenderList") {
val notifStats = calculateNotifStats(entries)
- if (FooterViewRefactor.isEnabled) {
- activeNotificationsInteractor.setNotifStats(notifStats)
- } else {
- controller.setNotifStats(notifStats)
- }
+ activeNotificationsInteractor.setNotifStats(notifStats)
renderListInteractor.setRenderedList(entries)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
index fbec6406e9d4..7e2361f24da9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt
@@ -26,7 +26,6 @@ import com.android.systemui.shared.notifications.domain.interactor.NotificationS
import com.android.systemui.statusbar.notification.NotificationActivityStarter.SettingsIntent
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterMessageViewModel
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -35,7 +34,6 @@ import dagger.assisted.AssistedInject
import java.util.Locale
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
@@ -57,9 +55,7 @@ constructor(
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
val areNotificationsHiddenInShade: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else if (ModesEmptyShadeFix.isEnabled) {
+ if (ModesEmptyShadeFix.isEnabled) {
zenModeInteractor.areNotificationsHiddenInShade
.dumpWhileCollecting("areNotificationsHiddenInShade")
.flowOn(bgDispatcher)
@@ -70,15 +66,10 @@ constructor(
}
}
- val hasFilteredOutSeenNotifications: StateFlow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- MutableStateFlow(false)
- } else {
- seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
- "hasFilteredOutSeenNotifications"
- )
- }
- }
+ val hasFilteredOutSeenNotifications: StateFlow<Boolean> =
+ seenNotificationsInteractor.hasFilteredOutSeenNotifications.dumpValue(
+ "hasFilteredOutSeenNotifications"
+ )
val text: Flow<String> by lazy {
if (ModesEmptyShadeFix.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
deleted file mode 100644
index 7e6044eb6869..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/shared/FooterViewRefactor.kt
+++ /dev/null
@@ -1,53 +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.systemui.statusbar.notification.footer.shared
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the FooterView refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object FooterViewRefactor {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.notificationsFooterViewRefactor()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index d25889820629..a670f69df601 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
@@ -41,7 +41,6 @@ import androidx.annotation.NonNull;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import com.android.systemui.statusbar.notification.row.FooterViewButton;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
@@ -63,16 +62,9 @@ public class FooterView extends StackScrollerDecorView {
private FooterViewButton mSettingsButton;
private FooterViewButton mHistoryButton;
private boolean mShouldBeHidden;
- private boolean mShowHistory;
- // String cache, for performance reasons.
- // Reading them from a Resources object can be quite slow sometimes.
- private String mManageNotificationText;
- private String mManageNotificationHistoryText;
// Footer label
private TextView mSeenNotifsFooterTextView;
- private String mSeenNotifsFilteredText;
- private Drawable mSeenNotifsFilteredIcon;
private @StringRes int mClearAllButtonTextId;
private @StringRes int mClearAllButtonDescriptionId;
@@ -159,8 +151,8 @@ public class FooterView extends StackScrollerDecorView {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
super.dump(pw, args);
DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ // TODO: b/375010573 - update dumps for redesign
pw.println("visibility: " + DumpUtilsKt.visibilityString(getVisibility()));
- pw.println("manageButton showHistory: " + mShowHistory);
pw.println("manageButton visibility: "
+ DumpUtilsKt.visibilityString(mClearAllButton.getVisibility()));
pw.println("dismissButton visibility: "
@@ -170,7 +162,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the text label for the "Clear all" button. */
public void setClearAllButtonText(@StringRes int textId) {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
if (mClearAllButtonTextId == textId) {
return; // nothing changed
}
@@ -187,9 +178,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the accessibility content description for the "Clear all" button. */
public void setClearAllButtonDescription(@StringRes int contentDescriptionId) {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- return;
- }
if (mClearAllButtonDescriptionId == contentDescriptionId) {
return; // nothing changed
}
@@ -207,7 +195,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the text label for the "Manage"/"History" button. */
public void setManageOrHistoryButtonText(@StringRes int textId) {
NotifRedesignFooter.assertInLegacyMode();
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
if (mManageOrHistoryButtonTextId == textId) {
return; // nothing changed
}
@@ -226,9 +213,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the accessibility content description for the "Clear all" button. */
public void setManageOrHistoryButtonDescription(@StringRes int contentDescriptionId) {
NotifRedesignFooter.assertInLegacyMode();
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- return;
- }
if (mManageOrHistoryButtonDescriptionId == contentDescriptionId) {
return; // nothing changed
}
@@ -247,7 +231,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the string for a message to be shown instead of the buttons. */
public void setMessageString(@StringRes int messageId) {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
if (mMessageStringId == messageId) {
return; // nothing changed
}
@@ -265,7 +248,6 @@ public class FooterView extends StackScrollerDecorView {
/** Set the icon to be shown before the message (see {@link #setMessageString(int)}). */
public void setMessageIcon(@DrawableRes int iconId) {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) return;
if (mMessageIconId == iconId) {
return; // nothing changed
}
@@ -303,32 +285,17 @@ public class FooterView extends StackScrollerDecorView {
mManageOrHistoryButton = findViewById(R.id.manage_text);
}
mSeenNotifsFooterTextView = findViewById(R.id.unlock_prompt_footer);
- if (!FooterViewRefactor.isEnabled()) {
- updateResources();
- }
updateContent();
updateColors();
}
/** Show a message instead of the footer buttons. */
public void setFooterLabelVisible(boolean isVisible) {
- // In the refactored code, hiding the buttons is handled in the FooterViewModel
- if (FooterViewRefactor.isEnabled()) {
- if (isVisible) {
- mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
- } else {
- mSeenNotifsFooterTextView.setVisibility(View.GONE);
- }
+ // Note: hiding the buttons is handled in the FooterViewModel
+ if (isVisible) {
+ mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
} else {
- if (isVisible) {
- mManageOrHistoryButton.setVisibility(View.GONE);
- mClearAllButton.setVisibility(View.GONE);
- mSeenNotifsFooterTextView.setVisibility(View.VISIBLE);
- } else {
- mManageOrHistoryButton.setVisibility(View.VISIBLE);
- mClearAllButton.setVisibility(View.VISIBLE);
- mSeenNotifsFooterTextView.setVisibility(View.GONE);
- }
+ mSeenNotifsFooterTextView.setVisibility(View.GONE);
}
}
@@ -359,10 +326,8 @@ public class FooterView extends StackScrollerDecorView {
/** Set onClickListener for the clear all (end) button. */
public void setClearAllButtonClickListener(OnClickListener listener) {
- if (FooterViewRefactor.isEnabled()) {
- if (mClearAllButtonClickListener == listener) return;
- mClearAllButtonClickListener = listener;
- }
+ if (mClearAllButtonClickListener == listener) return;
+ mClearAllButtonClickListener = listener;
mClearAllButton.setOnClickListener(listener);
}
@@ -379,62 +344,17 @@ public class FooterView extends StackScrollerDecorView {
|| touchY > mContent.getY() + mContent.getHeight();
}
- /** Show "History" instead of "Manage" on the start button. */
- public void showHistory(boolean showHistory) {
- FooterViewRefactor.assertInLegacyMode();
- if (mShowHistory == showHistory) {
- return;
- }
- mShowHistory = showHistory;
- updateContent();
- }
-
private void updateContent() {
- if (FooterViewRefactor.isEnabled()) {
- updateClearAllButtonText();
- updateClearAllButtonDescription();
-
- if (!NotifRedesignFooter.isEnabled()) {
- updateManageOrHistoryButtonText();
- updateManageOrHistoryButtonDescription();
- }
-
- updateMessageString();
- updateMessageIcon();
- } else {
- // NOTE: Prior to the refactor, `updateResources` set the class properties to the right
- // string values. It was always being called together with `updateContent`, which
- // deals with actually associating those string values with the correct views
- // (buttons or text).
- // In the new code, the resource IDs are being set in the view binder (through
- // setMessageString and similar setters). The setters themselves now deal with
- // updating both the resource IDs and the views where appropriate (as in, calling
- // `updateMessageString` when the resource ID changes). This eliminates the need for
- // `updateResources`, which will eventually be removed. There are, however, still
- // situations in which we want to update the views even if the resource IDs didn't
- // change, such as configuration changes.
- if (mShowHistory) {
- mManageOrHistoryButton.setText(mManageNotificationHistoryText);
- mManageOrHistoryButton.setContentDescription(mManageNotificationHistoryText);
- } else {
- mManageOrHistoryButton.setText(mManageNotificationText);
- mManageOrHistoryButton.setContentDescription(mManageNotificationText);
- }
-
- mClearAllButton.setText(R.string.clear_all_notifications_text);
- mClearAllButton.setContentDescription(
- mContext.getString(R.string.accessibility_clear_all));
+ updateClearAllButtonText();
+ updateClearAllButtonDescription();
- mSeenNotifsFooterTextView.setText(mSeenNotifsFilteredText);
- mSeenNotifsFooterTextView
- .setCompoundDrawablesRelative(mSeenNotifsFilteredIcon, null, null, null);
+ if (!NotifRedesignFooter.isEnabled()) {
+ updateManageOrHistoryButtonText();
+ updateManageOrHistoryButtonDescription();
}
- }
- /** Whether the start button shows "History" (true) or "Manage" (false). */
- public boolean isHistoryShown() {
- FooterViewRefactor.assertInLegacyMode();
- return mShowHistory;
+ updateMessageString();
+ updateMessageIcon();
}
@Override
@@ -445,9 +365,6 @@ public class FooterView extends StackScrollerDecorView {
}
super.onConfigurationChanged(newConfig);
updateColors();
- if (!FooterViewRefactor.isEnabled()) {
- updateResources();
- }
updateContent();
}
@@ -502,18 +419,6 @@ public class FooterView extends StackScrollerDecorView {
}
}
- private void updateResources() {
- FooterViewRefactor.assertInLegacyMode();
- mManageNotificationText = getContext().getString(R.string.manage_notifications_text);
- mManageNotificationHistoryText = getContext()
- .getString(R.string.manage_notifications_history_text);
- int unlockIconSize = getResources()
- .getDimensionPixelSize(R.dimen.notifications_unseen_footer_icon_size);
- mSeenNotifsFilteredText = getContext().getString(R.string.unlock_to_see_notif_text);
- mSeenNotifsFilteredIcon = getContext().getDrawable(R.drawable.ic_friction_lock_closed);
- mSeenNotifsFilteredIcon.setBounds(0, 0, unlockIconSize, unlockIconSize);
- }
-
@Override
@NonNull
public ExpandableViewState createExpandableViewState() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
index e724935e3ef4..5696e9f0c5a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModel.kt
@@ -27,7 +27,6 @@ import com.android.systemui.statusbar.notification.NotificationActivityStarter.S
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.util.kotlin.sample
import com.android.systemui.util.ui.AnimatableEvent
@@ -144,6 +143,7 @@ class FooterViewModel(
)
}
+// TODO: b/293167744 - remove this, use new viewmodel style
@Module
object FooterViewModelModule {
@Provides
@@ -153,18 +153,13 @@ object FooterViewModelModule {
notificationSettingsInteractor: Provider<NotificationSettingsInteractor>,
seenNotificationsInteractor: Provider<SeenNotificationsInteractor>,
shadeInteractor: Provider<ShadeInteractor>,
- ): Optional<FooterViewModel> {
- return if (FooterViewRefactor.isEnabled) {
- Optional.of(
- FooterViewModel(
- activeNotificationsInteractor.get(),
- notificationSettingsInteractor.get(),
- seenNotificationsInteractor.get(),
- shadeInteractor.get(),
- )
+ ): Optional<FooterViewModel> =
+ Optional.of(
+ FooterViewModel(
+ activeNotificationsInteractor.get(),
+ notificationSettingsInteractor.get(),
+ seenNotificationsInteractor.get(),
+ shadeInteractor.get(),
)
- } else {
- Optional.empty()
- }
- }
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 071d23283c43..76591ac4e453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -108,7 +108,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
import com.android.systemui.statusbar.notification.headsup.HeadsUpUtil;
@@ -703,9 +702,6 @@ public class NotificationStackScrollLayout
if (!ModesEmptyShadeFix.isEnabled()) {
inflateEmptyShadeView();
}
- if (!FooterViewRefactor.isEnabled()) {
- inflateFooterView();
- }
}
/**
@@ -741,22 +737,12 @@ public class NotificationStackScrollLayout
}
void reinflateViews() {
- if (!FooterViewRefactor.isEnabled()) {
- inflateFooterView();
- updateFooter();
- }
if (!ModesEmptyShadeFix.isEnabled()) {
inflateEmptyShadeView();
}
mSectionsManager.reinflateViews();
}
- public void setIsRemoteInputActive(boolean isActive) {
- FooterViewRefactor.assertInLegacyMode();
- mIsRemoteInputActive = isActive;
- updateFooter();
- }
-
void sendRemoteInputRowBottomBound(Float bottom) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
if (bottom != null) {
@@ -766,43 +752,6 @@ public class NotificationStackScrollLayout
mScrollViewFields.sendRemoteInputRowBottomBound(bottom);
}
- /** Setter for filtered notifs, to be removed with the FooterViewRefactor flag. */
- public void setHasFilteredOutSeenNotifications(boolean hasFilteredOutSeenNotifications) {
- FooterViewRefactor.assertInLegacyMode();
- mHasFilteredOutSeenNotifications = hasFilteredOutSeenNotifications;
- }
-
- @VisibleForTesting
- public void updateFooter() {
- FooterViewRefactor.assertInLegacyMode();
- if (mFooterView == null || mController == null) {
- return;
- }
- final boolean showHistory = mController.isHistoryEnabled();
- final boolean showDismissView = shouldShowDismissView();
-
- updateFooterView(shouldShowFooterView(showDismissView)/* visible */,
- showDismissView /* showDismissView */,
- showHistory/* showHistory */);
- }
-
- private boolean shouldShowDismissView() {
- FooterViewRefactor.assertInLegacyMode();
- return mController.hasActiveClearableNotifications(ROWS_ALL);
- }
-
- private boolean shouldShowFooterView(boolean showDismissView) {
- FooterViewRefactor.assertInLegacyMode();
- return (showDismissView || mController.getVisibleNotificationCount() > 0)
- && mIsCurrentUserSetup // see: b/193149550
- && !onKeyguard()
- && mUpcomingStatusBarState != StatusBarState.KEYGUARD
- // quick settings don't affect notifications when not in full screen
- && (getQsExpansionFraction() != 1 || !mQsFullScreen)
- && !mScreenOffAnimationController.shouldHideNotificationsFooter()
- && !mIsRemoteInputActive;
- }
-
void updateBgColor() {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
@@ -1861,9 +1810,6 @@ public class NotificationStackScrollLayout
*/
private float getAppearEndPosition() {
SceneContainerFlag.assertInLegacyMode();
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- return getAppearEndPositionLegacy();
- }
int appearPosition = mAmbientState.getStackTopMargin();
if (mEmptyShadeView.getVisibility() == GONE) {
@@ -1883,32 +1829,6 @@ public class NotificationStackScrollLayout
return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
}
- /**
- * The version of {@code getAppearEndPosition} that uses the notif count. The view shouldn't
- * need to know about that, so we want to phase this out with the footer view refactor.
- */
- private float getAppearEndPositionLegacy() {
- FooterViewRefactor.assertInLegacyMode();
-
- int appearPosition = mAmbientState.getStackTopMargin();
- int visibleNotifCount = mController.getVisibleNotificationCount();
- if (mEmptyShadeView.getVisibility() == GONE && visibleNotifCount > 0) {
- if (isHeadsUpTransition()
- || (mInHeadsUpPinnedMode && !mAmbientState.isDozing())) {
- if (mShelf.getVisibility() != GONE && visibleNotifCount > 1) {
- appearPosition += mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
- }
- appearPosition += getTopHeadsUpPinnedHeight()
- + getPositionInLinearLayout(mAmbientState.getTrackedHeadsUpRow());
- } else if (mShelf.getVisibility() != GONE) {
- appearPosition += mShelf.getIntrinsicHeight();
- }
- } else {
- appearPosition = mEmptyShadeView.getHeight();
- }
- return appearPosition + (onKeyguard() ? getTopPadding() : getIntrinsicPadding());
- }
-
private boolean isHeadsUpTransition() {
return mAmbientState.getTrackedHeadsUpRow() != null;
}
@@ -1928,8 +1848,7 @@ public class NotificationStackScrollLayout
// This can't use expansion fraction as that goes only from 0 to 1. Also when
// appear fraction for HUN is 0, expansion fraction will be already around 0.2-0.3
// and that makes translation jump immediately.
- float appearEndPosition = FooterViewRefactor.isEnabled() ? getAppearEndPosition()
- : getAppearEndPositionLegacy();
+ float appearEndPosition = getAppearEndPosition();
float appearStartPosition = getAppearStartPosition();
float hunAppearFraction = (height - appearStartPosition)
/ (appearEndPosition - appearStartPosition);
@@ -4848,15 +4767,6 @@ public class NotificationStackScrollLayout
}
}
- /**
- * Returns whether or not a History button is shown in the footer. If there is no footer, then
- * this will return false.
- **/
- public boolean isHistoryShown() {
- FooterViewRefactor.assertInLegacyMode();
- return mFooterView != null && mFooterView.isHistoryShown();
- }
-
/** Bind the {@link FooterView} to the NSSL. */
public void setFooterView(@NonNull FooterView footerView) {
int index = -1;
@@ -4866,18 +4776,6 @@ public class NotificationStackScrollLayout
}
mFooterView = footerView;
addView(mFooterView, index);
- if (!FooterViewRefactor.isEnabled()) {
- if (mManageButtonClickListener != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
- mFooterView.setClearAllButtonClickListener(v -> {
- if (mFooterClearAllListener != null) {
- mFooterClearAllListener.onClearAll();
- }
- clearNotifications(ROWS_ALL, true /* closeShade */);
- footerView.setClearAllButtonVisible(false /* visible */, true /* animate */);
- });
- }
}
public void setEmptyShadeView(EmptyShadeView emptyShadeView) {
@@ -4890,13 +4788,6 @@ public class NotificationStackScrollLayout
addView(mEmptyShadeView, index);
}
- /** Legacy version, should be removed with the footer refactor flag. */
- public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade) {
- FooterViewRefactor.assertInLegacyMode();
- updateEmptyShadeView(visible, areNotificationsHiddenInShade,
- mHasFilteredOutSeenNotifications);
- }
-
/** Trigger an update for the empty shade resources and visibility. */
public void updateEmptyShadeView(boolean visible, boolean areNotificationsHiddenInShade,
boolean hasFilteredOutSeenNotifications) {
@@ -4949,18 +4840,6 @@ public class NotificationStackScrollLayout
return mEmptyShadeView.isVisible();
}
- public void updateFooterView(boolean visible, boolean showDismissView, boolean showHistory) {
- FooterViewRefactor.assertInLegacyMode();
- if (mFooterView == null || mNotificationStackSizeCalculator == null) {
- return;
- }
- boolean animate = mIsExpanded && mAnimationsEnabled;
- mFooterView.setVisible(visible, animate);
- mFooterView.showHistory(showHistory);
- mFooterView.setClearAllButtonVisible(showDismissView, animate);
- mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
- }
-
@VisibleForTesting
public void setClearAllInProgress(boolean clearAllInProgress) {
mClearAllInProgress = clearAllInProgress;
@@ -5244,10 +5123,8 @@ public class NotificationStackScrollLayout
public void setQsFullScreen(boolean qsFullScreen) {
SceneContainerFlag.assertInLegacyMode();
- if (FooterViewRefactor.isEnabled()) {
- if (qsFullScreen == mQsFullScreen) {
- return; // no change
- }
+ if (qsFullScreen == mQsFullScreen) {
+ return; // no change
}
mQsFullScreen = qsFullScreen;
updateAlgorithmLayoutMinHeight();
@@ -5266,8 +5143,6 @@ public class NotificationStackScrollLayout
public void setQsExpansionFraction(float qsExpansionFraction) {
SceneContainerFlag.assertInLegacyMode();
- boolean footerAffected = getQsExpansionFraction() != qsExpansionFraction
- && (getQsExpansionFraction() == 1 || qsExpansionFraction == 1);
mQsExpansionFraction = qsExpansionFraction;
updateUseRoundedRectClipping();
@@ -5276,9 +5151,6 @@ public class NotificationStackScrollLayout
if (getOwnScrollY() > 0) {
setOwnScrollY((int) MathUtils.lerp(getOwnScrollY(), 0, getQsExpansionFraction()));
}
- if (!FooterViewRefactor.isEnabled() && footerAffected) {
- updateFooter();
- }
}
@VisibleForTesting
@@ -5456,14 +5328,6 @@ public class NotificationStackScrollLayout
requestChildrenUpdate();
}
- void setUpcomingStatusBarState(int upcomingStatusBarState) {
- FooterViewRefactor.assertInLegacyMode();
- mUpcomingStatusBarState = upcomingStatusBarState;
- if (mUpcomingStatusBarState != mStatusBarState) {
- updateFooter();
- }
- }
-
void onStatePostChange(boolean fromShadeLocked) {
boolean onKeyguard = onKeyguard();
@@ -5472,9 +5336,6 @@ public class NotificationStackScrollLayout
}
setExpandingEnabled(!onKeyguard);
- if (!FooterViewRefactor.isEnabled()) {
- updateFooter();
- }
requestChildrenUpdate();
onUpdateRowStates();
updateVisibility();
@@ -5490,8 +5351,7 @@ public class NotificationStackScrollLayout
if (mEmptyShadeView == null || mEmptyShadeView.getVisibility() == GONE) {
return getMinExpansionHeight();
} else {
- return FooterViewRefactor.isEnabled() ? getAppearEndPosition()
- : getAppearEndPositionLegacy();
+ return getAppearEndPosition();
}
}
@@ -5583,12 +5443,6 @@ public class NotificationStackScrollLayout
for (int i = 0; i < childCount; i++) {
ExpandableView child = getChildAtIndex(i);
child.dump(pw, args);
- if (!FooterViewRefactor.isEnabled()) {
- if (child instanceof FooterView) {
- DumpUtilsKt.withIncreasedIndent(pw,
- () -> dumpFooterViewVisibility(pw));
- }
- }
pw.println();
}
int transientViewCount = getTransientViewCount();
@@ -5615,45 +5469,6 @@ public class NotificationStackScrollLayout
pw.append(" bottomRadius=").println(mBgCornerRadii[4]);
}
- private void dumpFooterViewVisibility(IndentingPrintWriter pw) {
- FooterViewRefactor.assertInLegacyMode();
- final boolean showDismissView = shouldShowDismissView();
-
- pw.println("showFooterView: " + shouldShowFooterView(showDismissView));
- DumpUtilsKt.withIncreasedIndent(
- pw,
- () -> {
- pw.println("showDismissView: " + showDismissView);
- DumpUtilsKt.withIncreasedIndent(
- pw,
- () -> {
- pw.println(
- "hasActiveClearableNotifications: "
- + mController.hasActiveClearableNotifications(
- ROWS_ALL));
- });
- pw.println();
- pw.println("showHistory: " + mController.isHistoryEnabled());
- pw.println();
- pw.println(
- "visibleNotificationCount: "
- + mController.getVisibleNotificationCount());
- pw.println("mIsCurrentUserSetup: " + mIsCurrentUserSetup);
- pw.println("onKeyguard: " + onKeyguard());
- pw.println("mUpcomingStatusBarState: " + mUpcomingStatusBarState);
- if (!SceneContainerFlag.isEnabled()) {
- pw.println("QsExpansionFraction: " + getQsExpansionFraction());
- }
- pw.println("mQsFullScreen: " + mQsFullScreen);
- pw.println(
- "mScreenOffAnimationController"
- + ".shouldHideNotificationsFooter: "
- + mScreenOffAnimationController
- .shouldHideNotificationsFooter());
- pw.println("mIsRemoteInputActive: " + mIsRemoteInputActive);
- });
- }
-
public boolean isFullyHidden() {
return mAmbientState.isFullyHidden();
}
@@ -5764,14 +5579,6 @@ public class NotificationStackScrollLayout
clearNotifications(ROWS_GENTLE, closeShade, hideSilentSection);
}
- /** Legacy version of clearNotifications below. Uses the old data source for notif stats. */
- void clearNotifications(@SelectedRows int selection, boolean closeShade) {
- FooterViewRefactor.assertInLegacyMode();
- final boolean hideSilentSection = !mController.hasNotifications(
- ROWS_GENTLE, false /* clearable */);
- clearNotifications(selection, closeShade, hideSilentSection);
- }
-
/**
* Collects a list of visible rows, and animates them away in a staggered fashion as if they
* were dismissed. Notifications are dismissed in the backend via onClearAllAnimationsEnd.
@@ -5826,25 +5633,6 @@ public class NotificationStackScrollLayout
return canChildBeCleared(row) && matchesSelection(row, selection);
}
- /**
- * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
- */
- public void setManageButtonClickListener(@Nullable OnClickListener listener) {
- FooterViewRefactor.assertInLegacyMode();
- mManageButtonClickListener = listener;
- if (mFooterView != null) {
- mFooterView.setManageButtonClickListener(mManageButtonClickListener);
- }
- }
-
- @VisibleForTesting
- protected void inflateFooterView() {
- FooterViewRefactor.assertInLegacyMode();
- FooterView footerView = (FooterView) LayoutInflater.from(mContext).inflate(
- R.layout.status_bar_notification_footer, this, false);
- setFooterView(footerView);
- }
-
private void inflateEmptyShadeView() {
ModesEmptyShadeFix.assertInLegacyMode();
@@ -6091,11 +5879,6 @@ public class NotificationStackScrollLayout
mHighPriorityBeforeSpeedBump = highPriorityBeforeSpeedBump;
}
- void setFooterClearAllListener(FooterClearAllListener listener) {
- FooterViewRefactor.assertInLegacyMode();
- mFooterClearAllListener = listener;
- }
-
void setClearAllFinishedWhilePanelExpandedRunnable(Runnable runnable) {
mClearAllFinishedWhilePanelExpandedRunnable = runnable;
}
@@ -6394,17 +6177,6 @@ public class NotificationStackScrollLayout
}
/**
- * Sets whether the current user is set up, which is required to show the footer (b/193149550)
- */
- public void setCurrentUserSetup(boolean isCurrentUserSetup) {
- FooterViewRefactor.assertInLegacyMode();
- if (mIsCurrentUserSetup != isCurrentUserSetup) {
- mIsCurrentUserSetup = isCurrentUserSetup;
- updateFooter();
- }
- }
-
- /**
* Sets a {@link StackStateLogger} which is notified as the {@link StackStateAnimator} updates
* the views.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a33a9ed2df75..b892bebb3120 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -29,11 +29,8 @@ import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
-import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
import android.animation.ObjectAnimator;
import android.content.res.Configuration;
@@ -64,14 +61,10 @@ import com.android.internal.view.OneShotPreDrawListener;
import com.android.systemui.Dumpable;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
-import com.android.systemui.keyguard.shared.model.TransitionStep;
import com.android.systemui.media.controls.ui.controller.KeyguardMediaController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -92,18 +85,13 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
-import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
@@ -115,14 +103,15 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
import com.android.systemui.statusbar.notification.collection.provider.VisibilityLocationProviderDelegator;
+import com.android.systemui.statusbar.notification.collection.render.DefaultNotifStackController;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.NotifStackController;
-import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
-import com.android.systemui.statusbar.notification.dagger.SilentHeader;
-import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpNotificationViewControllerEmptyImpl;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.notification.headsup.HeadsUpTouchHelper.HeadsUpNotificationViewController;
+import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -137,13 +126,8 @@ import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
-import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
-import com.android.systemui.statusbar.notification.headsup.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
import com.android.systemui.statusbar.policy.SplitShadeStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.Compile;
import com.android.systemui.util.settings.SecureSettings;
@@ -179,10 +163,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private HeadsUpTouchHelper mHeadsUpTouchHelper;
private final NotificationRoundnessManager mNotificationRoundnessManager;
private final TunerService mTunerService;
- private final DeviceProvisionedController mDeviceProvisionedController;
private final DynamicPrivacyController mDynamicPrivacyController;
private final ConfigurationController mConfigurationController;
- private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
private final ColorUpdateLogger mColorUpdateLogger;
@@ -193,7 +175,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
private final UiEventLogger mUiEventLogger;
- private final NotificationRemoteInputManager mRemoteInputManager;
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
private final Provider<WindowRootView> mWindowRootView;
@@ -201,9 +182,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
private final PowerInteractor mPowerInteractor;
- private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final SectionHeaderController mSilentHeaderController;
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
private final InteractionJankMonitor mJankMonitor;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
@@ -211,8 +190,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotificationStackScrollLogger mLogger;
private final GroupExpansionManager mGroupExpansionManager;
- private final SeenNotificationsInteractor mSeenNotificationsInteractor;
- private final KeyguardTransitionRepository mKeyguardTransitionRepo;
private NotificationStackScrollLayout mView;
private TouchHandler mTouchHandler;
private NotificationSwipeHelper mSwipeHelper;
@@ -220,7 +197,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private Boolean mHistoryEnabled;
private int mBarState;
private HeadsUpAppearanceController mHeadsUpAppearanceController;
- private boolean mIsInTransitionToAod = false;
private final NotificationTargetsHelper mNotificationTargetsHelper;
private final SecureSettings mSecureSettings;
@@ -235,11 +211,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotificationListContainerImpl mNotificationListContainer =
new NotificationListContainerImpl();
+ // TODO: b/293167744 - Remove this.
private final NotifStackController mNotifStackController =
- new NotifStackControllerImpl();
-
- @Nullable
- private NotificationActivityStarter mNotificationActivityStarter;
+ new DefaultNotifStackController();
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
@@ -248,9 +222,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public void onViewAttachedToWindow(View v) {
mColorUpdateLogger.logTriggerEvent("NSSLC.onViewAttachedToWindow()");
mConfigurationController.addCallback(mConfigurationListener);
- if (!FooterViewRefactor.isEnabled()) {
- mZenModeController.addCallback(mZenModeControllerCallback);
- }
final int newBarState = mStatusBarStateController.getState();
if (newBarState != mBarState) {
mStateListener.onStateChanged(newBarState);
@@ -264,9 +235,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public void onViewDetachedFromWindow(View v) {
mColorUpdateLogger.logTriggerEvent("NSSLC.onViewDetachedFromWindow()");
mConfigurationController.removeCallback(mConfigurationListener);
- if (!FooterViewRefactor.isEnabled()) {
- mZenModeController.removeCallback(mZenModeControllerCallback);
- }
mStatusBarStateController.removeCallback(mStateListener);
}
};
@@ -287,28 +255,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
@Nullable
private ObjectAnimator mHideAlphaAnimator = null;
- private final DeviceProvisionedListener mDeviceProvisionedListener =
- new DeviceProvisionedListener() {
- @Override
- public void onDeviceProvisionedChanged() {
- updateCurrentUserIsSetup();
- }
-
- @Override
- public void onUserSwitched() {
- updateCurrentUserIsSetup();
- }
-
- @Override
- public void onUserSetupChanged() {
- updateCurrentUserIsSetup();
- }
-
- private void updateCurrentUserIsSetup() {
- mView.setCurrentUserSetup(mDeviceProvisionedController.isCurrentUserSetup());
- }
- };
-
private final Runnable mSensitiveStateChangedListener = new Runnable() {
@Override
public void run() {
@@ -318,20 +264,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
};
- private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
- if (!FooterViewRefactor.isEnabled()) {
- // Let's update the footer once the notifications have been updated (in the next frame)
- mView.post(this::updateFooter);
- }
- };
-
@VisibleForTesting
final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onDensityOrFontScaleChanged() {
- if (!FooterViewRefactor.isEnabled()) {
- updateShowEmptyShadeView();
- }
mView.reinflateViews();
}
@@ -351,10 +287,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.updateBgColor();
mView.updateDecorViews();
mView.reinflateViews();
- if (!FooterViewRefactor.isEnabled()) {
- updateShowEmptyShadeView();
- updateFooter();
- }
}
@Override
@@ -363,7 +295,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
};
- private NotifStats mNotifStats = NotifStats.getEmpty();
private float mMaxAlphaForKeyguard = 1.0f;
private String mMaxAlphaForKeyguardSource = "constructor";
private float mMaxAlphaForUnhide = 1.0f;
@@ -401,19 +332,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@Override
- public void onUpcomingStateChanged(int newState) {
- if (!FooterViewRefactor.isEnabled()) {
- mView.setUpcomingStatusBarState(newState);
- }
- }
-
- @Override
public void onStatePostChange() {
updateSensitivenessWithAnimation(mStatusBarStateController.goingToFullShade());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
- if (!FooterViewRefactor.isEnabled()) {
- updateImportantForAccessibility();
- }
}
};
@@ -422,9 +343,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public void onUserChanged(int userId) {
updateSensitivenessWithAnimation(false);
mHistoryEnabled = null;
- if (!FooterViewRefactor.isEnabled()) {
- updateFooter();
- }
}
};
@@ -656,7 +574,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
== null) {
mHeadsUpManager.removeNotification(
row.getEntry().getSbn().getKey(),
- /* removeImmediately= */ true ,
+ /* removeImmediately= */ true,
/* reason= */ "onChildSnappedBack"
);
}
@@ -714,14 +632,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
};
- private final ZenModeController.Callback mZenModeControllerCallback =
- new ZenModeController.Callback() {
- @Override
- public void onZenChanged(int zen) {
- updateShowEmptyShadeView();
- }
- };
-
@Inject
public NotificationStackScrollLayoutController(
NotificationStackScrollLayout view,
@@ -734,16 +644,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
Provider<IStatusBarService> statusBarService,
NotificationRoundnessManager notificationRoundnessManager,
TunerService tunerService,
- DeviceProvisionedController deviceProvisionedController,
DynamicPrivacyController dynamicPrivacyController,
@ShadeDisplayAware ConfigurationController configurationController,
SysuiStatusBarStateController statusBarStateController,
KeyguardMediaController keyguardMediaController,
KeyguardBypassController keyguardBypassController,
PowerInteractor powerInteractor,
- PrimaryBouncerInteractor primaryBouncerInteractor,
- KeyguardTransitionRepository keyguardTransitionRepo,
- ZenModeController zenModeController,
NotificationLockscreenUserManager lockscreenUserManager,
MetricsLogger metricsLogger,
ColorUpdateLogger colorUpdateLogger,
@@ -752,14 +658,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
FalsingManager falsingManager,
NotificationSwipeHelper.Builder notificationSwipeHelperBuilder,
GroupExpansionManager groupManager,
- @SilentHeader SectionHeaderController silentHeaderController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
UiEventLogger uiEventLogger,
- NotificationRemoteInputManager remoteInputManager,
VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
- SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
Provider<WindowRootView> windowRootView,
@@ -775,7 +678,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
SensitiveNotificationProtectionController sensitiveNotificationProtectionController,
WallpaperInteractor wallpaperInteractor) {
mView = view;
- mKeyguardTransitionRepo = keyguardTransitionRepo;
mViewBinder = viewBinder;
mStackStateLogger = stackLogger;
mLogger = logger;
@@ -795,15 +697,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
mNotificationRoundnessManager = notificationRoundnessManager;
mTunerService = tunerService;
- mDeviceProvisionedController = deviceProvisionedController;
mDynamicPrivacyController = dynamicPrivacyController;
mConfigurationController = configurationController;
mStatusBarStateController = statusBarStateController;
mKeyguardMediaController = keyguardMediaController;
mKeyguardBypassController = keyguardBypassController;
mPowerInteractor = powerInteractor;
- mPrimaryBouncerInteractor = primaryBouncerInteractor;
- mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
mMetricsLogger = metricsLogger;
mColorUpdateLogger = colorUpdateLogger;
@@ -815,13 +714,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mJankMonitor = jankMonitor;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
mGroupExpansionManager = groupManager;
- mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
mUiEventLogger = uiEventLogger;
- mRemoteInputManager = remoteInputManager;
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
- mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
mWindowRootView = windowRootView;
mNotificationTargetsHelper = notificationTargetsHelper;
@@ -850,18 +746,7 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
NotificationPanelEvent.fromSelection(selection)));
- if (!FooterViewRefactor.isEnabled()) {
- mView.setFooterClearAllListener(() ->
- mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES));
- mView.setIsRemoteInputActive(mRemoteInputManager.isRemoteInputActive());
- mRemoteInputManager.addControllerCallback(new RemoteInputController.Callback() {
- @Override
- public void onRemoteInputActive(boolean active) {
- mView.setIsRemoteInputActive(active);
- }
- });
- }
- mView.setClearAllFinishedWhilePanelExpandedRunnable(()-> {
+ mView.setClearAllFinishedWhilePanelExpandedRunnable(() -> {
final Runnable doCollapseRunnable = () ->
mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
mView.postDelayed(doCollapseRunnable, /* delayMillis = */ DELAY_BEFORE_SHADE_CLOSE);
@@ -889,19 +774,11 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
mKeyguardBypassController
.registerOnBypassStateChangedListener(mView::setKeyguardBypassEnabled);
- if (!FooterViewRefactor.isEnabled()) {
- mView.setManageButtonClickListener(v -> {
- if (mNotificationActivityStarter != null) {
- mNotificationActivityStarter.startHistoryIntent(v, mView.isHistoryShown());
- }
- });
- }
if (!SceneContainerFlag.isEnabled()) {
mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
}
mHeadsUpManager.setAnimationStateHandler(mView::setHeadsUpGoingAwayAnimationsAllowed);
- mDynamicPrivacyController.addListener(mDynamicPrivacyControllerListener);
mLockscreenShadeTransitionController.setStackScroller(this);
@@ -914,9 +791,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
switch (key) {
case Settings.Secure.NOTIFICATION_HISTORY_ENABLED:
mHistoryEnabled = null; // invalidate
- if (!FooterViewRefactor.isEnabled()) {
- updateFooter();
- }
break;
case HIGH_PRIORITY:
mView.setHighPriorityBeforeSpeedBump("1".equals(newValue));
@@ -938,12 +812,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return kotlin.Unit.INSTANCE;
});
- if (!FooterViewRefactor.isEnabled()) {
- // attach callback, and then call it to update mView immediately
- mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- mDeviceProvisionedListener.onDeviceProvisionedChanged();
- }
-
if (screenshareNotificationHiding()) {
mSensitiveNotificationProtectionController
.registerSensitiveStateListener(mSensitiveStateChangedListener);
@@ -953,20 +821,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
}
mView.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
- if (!FooterViewRefactor.isEnabled()) {
- mSilentHeaderController.setOnClearSectionClickListener(v -> clearSilentNotifications());
- }
mGroupExpansionManager.registerGroupExpansionChangeListener(
(changedRow, expanded) -> mView.onGroupExpandChanged(changedRow, expanded));
mViewBinder.bindWhileAttached(mView, this);
- if (!FooterViewRefactor.isEnabled()) {
- collectFlow(mView, mKeyguardTransitionRepo.getTransitions(),
- this::onKeyguardTransitionChanged);
- }
-
mView.setWallpaperInteractor(mWallpaperInteractor);
}
@@ -1168,11 +1028,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView != null && mView.isAddOrRemoveAnimationPending();
}
- public int getVisibleNotificationCount() {
- FooterViewRefactor.assertInLegacyMode();
- return mNotifStats.getNumActiveNotifs();
- }
-
public boolean isHistoryEnabled() {
Boolean historyEnabled = mHistoryEnabled;
if (historyEnabled == null) {
@@ -1284,9 +1139,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
public void setQsFullScreen(boolean fullScreen) {
mView.setQsFullScreen(fullScreen);
- if (!FooterViewRefactor.isEnabled()) {
- updateShowEmptyShadeView();
- }
}
public void setScrollingEnabled(boolean enabled) {
@@ -1464,64 +1316,12 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
/**
- * Set the visibility of the view, and propagate it to specific children.
+ * Set the visibility of the view.
*
* @param visible either the view is visible or not.
*/
public void updateVisibility(boolean visible) {
mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-
- // Refactor note: the empty shade's visibility doesn't seem to actually depend on the
- // parent visibility (so this update seemingly doesn't do anything). Therefore, this is not
- // modeled in the refactored code.
- if (!FooterViewRefactor.isEnabled() && mView.getVisibility() == View.VISIBLE) {
- // Synchronize EmptyShadeView visibility with the parent container.
- updateShowEmptyShadeView();
- updateImportantForAccessibility();
- }
- }
-
- /**
- * Update whether we should show the empty shade view ("no notifications" in the shade).
- * <p>
- * When in split mode, notifications are always visible regardless of the state of the
- * QuickSettings panel. That being the case, empty view is always shown if the other conditions
- * are true.
- */
- public void updateShowEmptyShadeView() {
- FooterViewRefactor.assertInLegacyMode();
-
- Trace.beginSection("NSSLC.updateShowEmptyShadeView");
-
- final boolean shouldShow = getVisibleNotificationCount() == 0
- && !mView.isQsFullScreen()
- // Hide empty shade view when in transition to AOD.
- // That avoids "No Notifications" to blink when transitioning to AOD.
- // For more details, see: b/228790482
- && !mIsInTransitionToAod
- // Don't show any notification content if the bouncer is showing. See b/267060171.
- && !mPrimaryBouncerInteractor.isBouncerShowing();
-
- mView.updateEmptyShadeView(shouldShow, mZenModeController.areNotificationsHiddenInShade());
-
- Trace.endSection();
- }
-
- /**
- * Update the importantForAccessibility of NotificationStackScrollLayout.
- * <p>
- * We want the NSSL to be unimportant for accessibility when there's no
- * notifications in it while the device is on lock screen, to avoid unlablel NSSL view.
- * Otherwise, we want it to be important for accessibility to enable accessibility
- * auto-scrolling in NSSL.
- */
- public void updateImportantForAccessibility() {
- FooterViewRefactor.assertInLegacyMode();
- if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
- mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
- } else {
- mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
- }
}
public boolean isShowingEmptyShadeView() {
@@ -1577,34 +1377,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.setPulsing(pulsing, animatePulse);
}
- /**
- * Return whether there are any clearable notifications
- */
- public boolean hasActiveClearableNotifications(@SelectedRows int selection) {
- FooterViewRefactor.assertInLegacyMode();
- return hasNotifications(selection, true /* clearable */);
- }
-
- public boolean hasNotifications(@SelectedRows int selection, boolean isClearable) {
- FooterViewRefactor.assertInLegacyMode();
- boolean hasAlertingMatchingClearable = isClearable
- ? mNotifStats.getHasClearableAlertingNotifs()
- : mNotifStats.getHasNonClearableAlertingNotifs();
- boolean hasSilentMatchingClearable = isClearable
- ? mNotifStats.getHasClearableSilentNotifs()
- : mNotifStats.getHasNonClearableSilentNotifs();
- switch (selection) {
- case ROWS_GENTLE:
- return hasSilentMatchingClearable;
- case ROWS_HIGH_PRIORITY:
- return hasAlertingMatchingClearable;
- case ROWS_ALL:
- return hasSilentMatchingClearable || hasAlertingMatchingClearable;
- default:
- throw new IllegalStateException("Bad selection: " + selection);
- }
- }
-
/** Sets whether the NSSL is displayed over the unoccluded Lockscreen. */
public void setOnLockscreen(boolean isOnLockscreen) {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) return;
@@ -1637,9 +1409,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
entry.notifyHeightChanged(true /* needsAnimation */);
- if (!FooterViewRefactor.isEnabled()) {
- updateFooter();
- }
}
public void lockScrollTo(NotificationEntry entry) {
@@ -1662,13 +1431,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
};
}
- public void updateFooter() {
- FooterViewRefactor.assertInLegacyMode();
- Trace.beginSection("NSSLC.updateFooter");
- mView.updateFooter();
- Trace.endSection();
- }
-
public void onUpdateRowStates() {
mView.onUpdateRowStates();
}
@@ -1695,18 +1457,10 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getTransientViewCount();
}
- public View getTransientView(int i) {
- return mView.getTransientView(i);
- }
-
public NotificationStackScrollLayout getView() {
return mView;
}
- public float calculateGapHeight(ExpandableView previousView, ExpandableView child, int count) {
- return mView.calculateGapHeight(previousView, child, count);
- }
-
NotificationRoundnessManager getNotificationRoundnessManager() {
return mNotificationRoundnessManager;
}
@@ -1772,13 +1526,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return NotificationSwipeHelper.isTouchInView(event, view);
}
- public void clearSilentNotifications() {
- FooterViewRefactor.assertInLegacyMode();
- // Leave the shade open if there will be other notifs left over to clear
- final boolean closeShade = !hasActiveClearableNotifications(ROWS_HIGH_PRIORITY);
- mView.clearNotifications(ROWS_GENTLE, closeShade);
- }
-
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
@SelectedRows int selectedRows) {
if (selectedRows == ROWS_ALL) {
@@ -1880,10 +1627,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mView.animateNextTopPaddingChange();
}
- public void setNotificationActivityStarter(NotificationActivityStarter activityStarter) {
- mNotificationActivityStarter = activityStarter;
- }
-
public NotificationTargetsHelper getNotificationTargetsHelper() {
return mNotificationTargetsHelper;
}
@@ -1898,18 +1641,6 @@ public class NotificationStackScrollLayoutController implements Dumpable {
}
@VisibleForTesting
- void onKeyguardTransitionChanged(TransitionStep transitionStep) {
- FooterViewRefactor.assertInLegacyMode();
- boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
- && (transitionStep.getFrom().equals(KeyguardState.GONE)
- || transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
- if (mIsInTransitionToAod != isTransitionToAod) {
- mIsInTransitionToAod = isTransitionToAod;
- updateShowEmptyShadeView();
- }
- }
-
- @VisibleForTesting
TouchHandler getTouchHandler() {
return mTouchHandler;
}
@@ -2288,22 +2019,4 @@ public class NotificationStackScrollLayoutController implements Dumpable {
&& !mSwipeHelper.isSwiping();
}
}
-
- private class NotifStackControllerImpl implements NotifStackController {
- @Override
- public void setNotifStats(@NonNull NotifStats notifStats) {
- FooterViewRefactor.assertInLegacyMode();
- mNotifStats = notifStats;
-
- if (!FooterViewRefactor.isEnabled()) {
- mView.setHasFilteredOutSeenNotifications(
- mSeenNotificationsInteractor
- .getHasFilteredOutSeenNotifications().getValue());
-
- updateFooter();
- updateShowEmptyShadeView();
- updateImportantForAccessibility();
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 1653029dc994..06b989aaab57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -35,7 +35,6 @@ import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.SourceType;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -463,26 +462,23 @@ public class StackScrollAlgorithm {
if (v == ambientState.getShelf()) {
continue;
}
- if (FooterViewRefactor.isEnabled()) {
- if (v instanceof EmptyShadeView) {
- emptyShadeVisible = true;
- }
- if (v instanceof FooterView footerView) {
- if (emptyShadeVisible || notGoneIndex == 0) {
- // if the empty shade is visible or the footer is the first visible
- // view, we're in a transitory state so let's leave the footer alone.
- if (Flags.notificationsFooterVisibilityFix()
- && !SceneContainerFlag.isEnabled()) {
- // ...except for the hidden state, to prevent it from flashing on
- // the screen (this piece is copied from updateChild, and is not
- // necessary in flexiglass).
- if (footerView.shouldBeHidden()
- || !ambientState.isShadeExpanded()) {
- footerView.getViewState().hidden = true;
- }
+ if (v instanceof EmptyShadeView) {
+ emptyShadeVisible = true;
+ }
+ if (v instanceof FooterView footerView) {
+ if (emptyShadeVisible || notGoneIndex == 0) {
+ // if the empty shade is visible or the footer is the first visible
+ // view, we're in a transitory state so let's leave the footer alone.
+ if (Flags.notificationsFooterVisibilityFix()
+ && !SceneContainerFlag.isEnabled()) {
+ // ...except for the hidden state, to prevent it from flashing on
+ // the screen (this piece is copied from updateChild, and is not
+ // necessary in flexiglass).
+ if (footerView.shouldBeHidden() || !ambientState.isShadeExpanded()) {
+ footerView.getViewState().hidden = true;
}
- continue;
}
+ continue;
}
}
@@ -699,44 +695,28 @@ public class StackScrollAlgorithm {
viewEnd, /* hunMax */ ambientState.getMaxHeadsUpTranslation()
);
if (view instanceof FooterView) {
- if (FooterViewRefactor.isEnabled()) {
- if (SceneContainerFlag.isEnabled()) {
- final float footerEnd =
- stackTop + viewState.getYTranslation() + view.getIntrinsicHeight();
- final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff();
- ((FooterView.FooterViewState) viewState).hideContent =
- noSpaceForFooter || (ambientState.isClearAllInProgress()
- && !hasNonClearableNotifs(algorithmState));
- } else {
- // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
- // already, so we shouldn't need to use ambientState here. However,
- // currently it doesn't get updated quickly enough and can cause the footer to
- // flash when closing the shade. As such, we temporarily also check the
- // ambientState directly.
- if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
- viewState.hidden = true;
- } else {
- final float footerEnd = algorithmState.mCurrentExpandedYPosition
- + view.getIntrinsicHeight();
- final boolean noSpaceForFooter =
- footerEnd > ambientState.getStackEndHeight();
- ((FooterView.FooterViewState) viewState).hideContent =
- noSpaceForFooter || (ambientState.isClearAllInProgress()
- && !hasNonClearableNotifs(algorithmState));
- }
- }
+ if (SceneContainerFlag.isEnabled()) {
+ final float footerEnd =
+ stackTop + viewState.getYTranslation() + view.getIntrinsicHeight();
+ final boolean noSpaceForFooter = footerEnd > ambientState.getStackCutoff();
+ ((FooterView.FooterViewState) viewState).hideContent =
+ noSpaceForFooter || (ambientState.isClearAllInProgress()
+ && !hasNonClearableNotifs(algorithmState));
} else {
- final boolean shadeClosed = !ambientState.isShadeExpanded();
- final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
- if (shadeClosed) {
+ // TODO(b/333445519): shouldBeHidden should reflect whether the shade is closed
+ // already, so we shouldn't need to use ambientState here. However,
+ // currently it doesn't get updated quickly enough and can cause the footer to
+ // flash when closing the shade. As such, we temporarily also check the
+ // ambientState directly.
+ if (((FooterView) view).shouldBeHidden() || !ambientState.isShadeExpanded()) {
viewState.hidden = true;
} else {
final float footerEnd = algorithmState.mCurrentExpandedYPosition
+ view.getIntrinsicHeight();
- final boolean noSpaceForFooter = footerEnd > ambientState.getStackEndHeight();
+ final boolean noSpaceForFooter =
+ footerEnd > ambientState.getStackEndHeight();
((FooterView.FooterViewState) viewState).hideContent =
- isShelfShowing || noSpaceForFooter
- || (ambientState.isClearAllInProgress()
+ noSpaceForFooter || (ambientState.isClearAllInProgress()
&& !hasNonClearableNotifs(algorithmState));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
index b4561686b7b2..1d7e658932ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt
@@ -40,7 +40,6 @@ import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyS
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView
import com.android.systemui.statusbar.notification.emptyshade.ui.viewbinder.EmptyShadeViewBinder
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView
import com.android.systemui.statusbar.notification.footer.ui.viewbinder.FooterViewBinder
@@ -108,25 +107,20 @@ constructor(
launch { bindShelf(shelf) }
bindHideList(viewController, viewModel, hiderTracker)
- if (FooterViewRefactor.isEnabled) {
- val hasNonClearableSilentNotifications: StateFlow<Boolean> =
- viewModel.hasNonClearableSilentNotifications.stateIn(this)
- launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
- launch {
- if (ModesEmptyShadeFix.isEnabled) {
- reinflateAndBindEmptyShade(view)
- } else {
- bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
- }
+ val hasNonClearableSilentNotifications: StateFlow<Boolean> =
+ viewModel.hasNonClearableSilentNotifications.stateIn(this)
+ launch { reinflateAndBindFooter(view, hasNonClearableSilentNotifications) }
+ launch {
+ if (ModesEmptyShadeFix.isEnabled) {
+ reinflateAndBindEmptyShade(view)
+ } else {
+ bindEmptyShadeLegacy(viewModel.emptyShadeViewFactory.create(), view)
}
- launch {
- bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications)
- }
- launch {
- viewModel.isImportantForAccessibility.collect { isImportantForAccessibility
- ->
- view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
- }
+ }
+ launch { bindSilentHeaderClickListener(view, hasNonClearableSilentNotifications) }
+ launch {
+ viewModel.isImportantForAccessibility.collect { isImportantForAccessibility ->
+ view.setImportantForAccessibilityYesNo(isImportantForAccessibility)
}
}
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 ea714608ea66..0b2b84e60f4b 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,7 +28,6 @@ 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.statusbar.notification.footer.shared.FooterViewRefactor
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
@@ -81,9 +80,6 @@ constructor(
controller.setOverExpansion(0f)
controller.setOverScrollAmount(0)
- if (!FooterViewRefactor.isEnabled) {
- controller.updateFooter()
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
index 38390e7bdb39..fcc671a5bae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModel.kt
@@ -25,7 +25,6 @@ import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotif
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix
import com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel.EmptyShadeViewModel
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModel
import com.android.systemui.statusbar.notification.shared.HeadsUpRowKey
import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel
@@ -75,46 +74,37 @@ constructor(
* we want it to be important for accessibility to enable accessibility auto-scrolling in NSSL.
* See b/242235264 for more details.
*/
- val isImportantForAccessibility: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(true)
- } else {
- combine(
- activeNotificationsInteractor.areAnyNotificationsPresent,
- notificationStackInteractor.isShowingOnLockscreen,
- ) { hasNotifications, isShowingOnLockscreen ->
- hasNotifications || !isShowingOnLockscreen
- }
- .distinctUntilChanged()
- .dumpWhileCollecting("isImportantForAccessibility")
- .flowOn(bgDispatcher)
- }
- }
+ val isImportantForAccessibility: Flow<Boolean> =
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ notificationStackInteractor.isShowingOnLockscreen,
+ ) { hasNotifications, isShowingOnLockscreen ->
+ hasNotifications || !isShowingOnLockscreen
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("isImportantForAccessibility")
+ .flowOn(bgDispatcher)
val shouldShowEmptyShadeView: Flow<Boolean> by lazy {
ModesEmptyShadeFix.assertInLegacyMode()
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- combine(
- activeNotificationsInteractor.areAnyNotificationsPresent,
- shadeInteractor.isQsFullscreen,
- notificationStackInteractor.isShowingOnLockscreen,
- ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
- when {
- hasNotifications -> false
- isQsFullScreen -> false
- // Do not show the empty shade if the lockscreen is visible (including AOD
- // b/228790482 and bouncer b/267060171), except if the shade is opened on
- // top.
- isShowingOnLockscreen -> false
- else -> true
- }
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ shadeInteractor.isQsFullscreen,
+ notificationStackInteractor.isShowingOnLockscreen,
+ ) { hasNotifications, isQsFullScreen, isShowingOnLockscreen ->
+ when {
+ hasNotifications -> false
+ isQsFullScreen -> false
+ // Do not show the empty shade if the lockscreen is visible (including AOD
+ // b/228790482 and bouncer b/267060171), except if the shade is opened on
+ // top.
+ isShowingOnLockscreen -> false
+ else -> true
}
- .distinctUntilChanged()
- .dumpWhileCollecting("shouldShowEmptyShadeView")
- .flowOn(bgDispatcher)
- }
+ }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("shouldShowEmptyShadeView")
+ .flowOn(bgDispatcher)
}
val shouldShowEmptyShadeViewAnimated: Flow<AnimatedValue<Boolean>> by lazy {
@@ -164,18 +154,14 @@ constructor(
*/
val shouldHideFooterView: Flow<Boolean> by lazy {
SceneContainerFlag.assertInLegacyMode()
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- // When the shade is closed, the footer is still present in the list, but not visible.
- // This prevents the footer from being shown when a HUN is present, while still allowing
- // the footer to be counted as part of the shade for measurements.
- shadeInteractor.shadeExpansion
- .map { it == 0f }
- .distinctUntilChanged()
- .dumpWhileCollecting("shouldHideFooterView")
- .flowOn(bgDispatcher)
- }
+ // When the shade is closed, the footer is still present in the list, but not visible.
+ // This prevents the footer from being shown when a HUN is present, while still allowing
+ // the footer to be counted as part of the shade for measurements.
+ shadeInteractor.shadeExpansion
+ .map { it == 0f }
+ .distinctUntilChanged()
+ .dumpWhileCollecting("shouldHideFooterView")
+ .flowOn(bgDispatcher)
}
/**
@@ -188,68 +174,64 @@ constructor(
*/
val shouldIncludeFooterView: Flow<AnimatedValue<Boolean>> by lazy {
SceneContainerFlag.assertInLegacyMode()
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(AnimatedValue.NotAnimating(false))
- } else {
- combine(
- activeNotificationsInteractor.areAnyNotificationsPresent,
- userSetupInteractor.isUserSetUp,
- notificationStackInteractor.isShowingOnLockscreen,
- shadeInteractor.isQsFullscreen,
- remoteInputInteractor.isRemoteInputActive,
- ) {
- hasNotifications,
- isUserSetUp,
- isShowingOnLockscreen,
- qsFullScreen,
- isRemoteInputActive ->
- when {
- !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
- // Hide the footer until the user setup is complete, to prevent access
- // to settings (b/193149550).
- !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
- // Do not show the footer if the lockscreen is visible (incl. AOD),
- // except if the shade is opened on top. See also b/219680200.
- // Do not animate, as that makes the footer appear briefly when
- // transitioning between the shade and keyguard.
- isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION
- // Do not show the footer if quick settings are fully expanded (except
- // for the foldable split shade view). See b/201427195 && b/222699879.
- qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
- // Hide the footer if remote input is active (i.e. user is replying to a
- // notification). See b/75984847.
- isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
- else -> VisibilityChange.APPEAR_WITH_ANIMATION
- }
+ combine(
+ activeNotificationsInteractor.areAnyNotificationsPresent,
+ userSetupInteractor.isUserSetUp,
+ notificationStackInteractor.isShowingOnLockscreen,
+ shadeInteractor.isQsFullscreen,
+ remoteInputInteractor.isRemoteInputActive,
+ ) {
+ hasNotifications,
+ isUserSetUp,
+ isShowingOnLockscreen,
+ qsFullScreen,
+ isRemoteInputActive ->
+ when {
+ !hasNotifications -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Hide the footer until the user setup is complete, to prevent access
+ // to settings (b/193149550).
+ !isUserSetUp -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Do not show the footer if the lockscreen is visible (incl. AOD),
+ // except if the shade is opened on top. See also b/219680200.
+ // Do not animate, as that makes the footer appear briefly when
+ // transitioning between the shade and keyguard.
+ isShowingOnLockscreen -> VisibilityChange.DISAPPEAR_WITHOUT_ANIMATION
+ // Do not show the footer if quick settings are fully expanded (except
+ // for the foldable split shade view). See b/201427195 && b/222699879.
+ qsFullScreen -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ // Hide the footer if remote input is active (i.e. user is replying to a
+ // notification). See b/75984847.
+ isRemoteInputActive -> VisibilityChange.DISAPPEAR_WITH_ANIMATION
+ else -> VisibilityChange.APPEAR_WITH_ANIMATION
}
- .distinctUntilChanged(
- // Equivalent unless visibility changes
- areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
- a.visible == b.visible
- }
- )
- // Should we animate the visibility change?
- .sample(
- // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
- // but instead it should be a field in ShadeAnimationInteractor.
- combine(
- shadeInteractor.isShadeFullyExpanded,
- shadeInteractor.isShadeTouchable,
- ::Pair,
- )
- .onStart { emit(Pair(false, false)) }
- ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
- // Animate if the shade is interactive, but NOT on the lockscreen. Having
- // animations enabled while on the lockscreen makes the footer appear briefly
- // when transitioning between the shade and keyguard.
- val shouldAnimate =
- isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate
- AnimatableEvent(visibilityChange.visible, shouldAnimate)
+ }
+ .distinctUntilChanged(
+ // Equivalent unless visibility changes
+ areEquivalent = { a: VisibilityChange, b: VisibilityChange ->
+ a.visible == b.visible
}
- .toAnimatedValueFlow()
- .dumpWhileCollecting("shouldIncludeFooterView")
- .flowOn(bgDispatcher)
- }
+ )
+ // Should we animate the visibility change?
+ .sample(
+ // TODO(b/322167853): This check is currently duplicated in FooterViewModel,
+ // but instead it should be a field in ShadeAnimationInteractor.
+ combine(
+ shadeInteractor.isShadeFullyExpanded,
+ shadeInteractor.isShadeTouchable,
+ ::Pair,
+ )
+ .onStart { emit(Pair(false, false)) }
+ ) { visibilityChange, (isShadeFullyExpanded, animationsEnabled) ->
+ // Animate if the shade is interactive, but NOT on the lockscreen. Having
+ // animations enabled while on the lockscreen makes the footer appear briefly
+ // when transitioning between the shade and keyguard.
+ val shouldAnimate =
+ isShadeFullyExpanded && animationsEnabled && visibilityChange.canAnimate
+ AnimatableEvent(visibilityChange.visible, shouldAnimate)
+ }
+ .toAnimatedValueFlow()
+ .dumpWhileCollecting("shouldIncludeFooterView")
+ .flowOn(bgDispatcher)
}
// This flow replaces shouldHideFooterView+shouldIncludeFooterView in flexiglass.
@@ -328,25 +310,15 @@ constructor(
APPEAR_WITH_ANIMATION(visible = true, canAnimate = true),
}
- val hasClearableAlertingNotifications: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
- "hasClearableAlertingNotifications"
- )
- }
- }
+ val hasClearableAlertingNotifications: Flow<Boolean> =
+ activeNotificationsInteractor.hasClearableAlertingNotifications.dumpWhileCollecting(
+ "hasClearableAlertingNotifications"
+ )
- val hasNonClearableSilentNotifications: Flow<Boolean> by lazy {
- if (FooterViewRefactor.isUnexpectedlyInLegacyMode()) {
- flowOf(false)
- } else {
- activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
- "hasNonClearableSilentNotifications"
- )
- }
- }
+ val hasNonClearableSilentNotifications: Flow<Boolean> =
+ activeNotificationsInteractor.hasNonClearableSilentNotifications.dumpWhileCollecting(
+ "hasNonClearableSilentNotifications"
+ )
val topHeadsUpRow: Flow<HeadsUpRowKey?> by lazy {
if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 1474789ea0e3..3d6cd7e49dfe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -1487,8 +1487,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mActivityTransitionAnimator.setCallback(mActivityTransitionAnimatorCallback);
mActivityTransitionAnimator.addListener(mActivityTransitionAnimatorListener);
mRemoteInputManager.addControllerCallback(mNotificationShadeWindowController);
- mStackScrollerController.setNotificationActivityStarter(
- mNotificationActivityStarterLazy.get());
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarterLazy.get());
mShadeController.setNotificationPresenter(mPresenterLazy.get());
mNotificationsController.initialize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3749b96199f6..8443edd6aa87 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import static android.view.WindowInsets.Type.navigationBars;
-import static com.android.systemui.Flags.predictiveBackAnimateBouncer;
import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -328,7 +327,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private float mQsExpansion;
final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
- private boolean mIsBackAnimationEnabled;
private final UdfpsOverlayInteractor mUdfpsOverlayInteractor;
private final ActivityStarter mActivityStarter;
@@ -434,7 +432,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mAlternateBouncerInteractor = alternateBouncerInteractor;
mBouncerInteractor = bouncerInteractor;
- mIsBackAnimationEnabled = predictiveBackAnimateBouncer();
mUdfpsOverlayInteractor = udfpsOverlayInteractor;
mActivityStarter = activityStarter;
mKeyguardTransitionInteractor = keyguardTransitionInteractor;
@@ -630,7 +627,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private boolean shouldPlayBackAnimation() {
// Suppress back animation when bouncer shouldn't be dismissed on back invocation.
- return !needsFullscreenBouncer() && mIsBackAnimationEnabled;
+ return !needsFullscreenBouncer();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 03324d2a3e6a..c47ed1722bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Flags.predictiveBackAnimateDialogs;
-
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.BroadcastReceiver;
@@ -285,15 +283,13 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
for (int i = 0; i < mOnCreateRunnables.size(); i++) {
mOnCreateRunnables.get(i).run();
}
- if (predictiveBackAnimateDialogs()) {
- View targetView = getWindow().getDecorView();
- DialogKt.registerAnimationOnBackInvoked(
- /* dialog = */ this,
- /* targetView = */ targetView,
- /* backAnimationSpec= */mDelegate.getBackAnimationSpec(
- () -> targetView.getResources().getDisplayMetrics())
- );
- }
+ View targetView = getWindow().getDecorView();
+ DialogKt.registerAnimationOnBackInvoked(
+ /* dialog = */ this,
+ /* targetView = */ targetView,
+ /* backAnimationSpec= */mDelegate.getBackAnimationSpec(
+ () -> targetView.getResources().getDisplayMetrics())
+ );
}
private void updateWindowSize() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
index 7e06c35315f9..0dd7c8499861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/HomeStatusBarViewBinder.kt
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.events.shared.model.SystemEventAnimationSt
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.ConnectedDisplaysStatusBarNotificationIconViewStore
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
+import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel
import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.HomeStatusBarViewModel.VisibilityModel
import javax.inject.Inject
@@ -115,7 +116,11 @@ constructor(
}
}
- if (Flags.statusBarScreenSharingChips() && !StatusBarNotifChips.isEnabled) {
+ if (
+ Flags.statusBarScreenSharingChips() &&
+ !StatusBarNotifChips.isEnabled &&
+ !StatusBarChipsModernization.isEnabled
+ ) {
val primaryChipView: View =
view.requireViewById(R.id.ongoing_activity_chip_primary)
launch {
@@ -157,7 +162,11 @@ constructor(
}
}
- if (Flags.statusBarScreenSharingChips() && StatusBarNotifChips.isEnabled) {
+ if (
+ Flags.statusBarScreenSharingChips() &&
+ StatusBarNotifChips.isEnabled &&
+ !StatusBarChipsModernization.isEnabled
+ ) {
val primaryChipView: View =
view.requireViewById(R.id.ongoing_activity_chip_primary)
val secondaryChipView: View =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
index 7243ba7def58..f286a1a148fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/composable/StatusBarRoot.kt
@@ -164,11 +164,19 @@ fun StatusBarRoot(
statusBarViewModel.iconBlockList,
)
- if (!StatusBarChipsModernization.isEnabled) {
+ if (StatusBarChipsModernization.isEnabled) {
+ // Make sure the primary chip is hidden when StatusBarChipsModernization is
+ // enabled. OngoingActivityChips will be shown in a composable container
+ // when this flag is enabled.
+ phoneStatusBarView
+ .requireViewById<View>(R.id.ongoing_activity_chip_primary)
+ .visibility = View.GONE
+ } else {
ongoingCallController.setChipView(
phoneStatusBarView.requireViewById(R.id.ongoing_activity_chip_primary)
)
}
+
// For notifications, first inflate the [NotificationIconContainer]
val notificationIconArea =
phoneStatusBarView.requireViewById<ViewGroup>(R.id.notification_icon_area)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 31cae79c6b94..81d06a8db0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -32,6 +32,7 @@ import android.os.Trace;
import androidx.annotation.VisibleForTesting;
+import com.android.app.tracing.coroutines.TrackTracer;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -241,7 +242,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController {
private void setKeyguardFadingAway(boolean keyguardFadingAway) {
if (mKeyguardFadingAway != keyguardFadingAway) {
- Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway",
+ TrackTracer.instantForGroup("keyguard", "FadingAway",
keyguardFadingAway ? 1 : 0);
mKeyguardFadingAway = keyguardFadingAway;
invokeForEachCallback(Callback::onKeyguardFadingAwayChanged);
@@ -356,7 +357,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController {
@Override
public void notifyKeyguardGoingAway(boolean keyguardGoingAway) {
if (mKeyguardGoingAway != keyguardGoingAway) {
- Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
+ Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguard##GoingAway",
keyguardGoingAway ? 1 : 0);
mKeyguardGoingAway = keyguardGoingAway;
mKeyguardInteractorLazy.get().setIsKeyguardGoingAway(keyguardGoingAway);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
index 12ed647fdee7..fdc2d8d96f9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/ZenModeInteractor.kt
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.policy.domain.interactor
-import android.app.NotificationManager.INTERRUPTION_FILTER_NONE
import android.content.Context
+import android.media.AudioManager
import android.provider.Settings
import android.provider.Settings.Secure.ZEN_DURATION_FOREVER
import android.provider.Settings.Secure.ZEN_DURATION_PROMPT
@@ -29,6 +29,7 @@ import com.android.settingslib.notification.data.repository.ZenModeRepository
import com.android.settingslib.notification.modes.ZenIcon
import com.android.settingslib.notification.modes.ZenIconLoader
import com.android.settingslib.notification.modes.ZenMode
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.modes.shared.ModesUi
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
@@ -67,6 +68,17 @@ constructor(
deviceProvisioningRepository: DeviceProvisioningRepository,
userSetupRepository: UserSetupRepository,
) {
+ /**
+ * List of predicates to determine if the [ZenMode] blocks an audio stream. Typical use case
+ * would be: `zenModeByStreamPredicates[stream](zenMode)`
+ */
+ private val zenModeByStreamPredicates =
+ mapOf<Int, (ZenMode) -> Boolean>(
+ AudioManager.STREAM_MUSIC to { it.policy.priorityCategoryMedia == STATE_DISALLOW },
+ AudioManager.STREAM_ALARM to { it.policy.priorityCategoryAlarms == STATE_DISALLOW },
+ AudioManager.STREAM_SYSTEM to { it.policy.priorityCategorySystem == STATE_DISALLOW },
+ )
+
val isZenAvailable: Flow<Boolean> =
combine(
deviceProvisioningRepository.isDeviceProvisioned,
@@ -125,21 +137,16 @@ constructor(
.flowOn(bgDispatcher)
.distinctUntilChanged()
- val activeModesBlockingEverything: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
- mode.interruptionFilter == INTERRUPTION_FILTER_NONE
- }
-
- val activeModesBlockingMedia: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
- mode.policy.priorityCategoryMedia == STATE_DISALLOW
- }
-
- val activeModesBlockingAlarms: Flow<ActiveZenModes> = getFilteredActiveModesFlow { mode ->
- mode.policy.priorityCategoryAlarms == STATE_DISALLOW
- }
+ fun canBeBlockedByZenMode(stream: AudioStream): Boolean =
+ zenModeByStreamPredicates.containsKey(stream.value)
- private fun getFilteredActiveModesFlow(predicate: (ZenMode) -> Boolean): Flow<ActiveZenModes> {
+ fun activeModesBlockingStream(stream: AudioStream): Flow<ActiveZenModes> {
+ val isBlockingStream = zenModeByStreamPredicates[stream.value]
+ require(isBlockingStream != null) {
+ "$stream is unsupported. Use canBeBlockedByZenMode to check if the stream can be affected by the Zen Mode."
+ }
return modes
- .map { modes -> modes.filter { mode -> predicate(mode) } }
+ .map { modes -> modes.filter { isBlockingStream(it) } }
.map { modes -> buildActiveZenModes(modes) }
.flowOn(bgDispatcher)
.distinctUntilChanged()
@@ -194,7 +201,6 @@ constructor(
)
null
}
-
ZEN_DURATION_FOREVER -> null
else -> Duration.ofMinutes(zenDuration.toLong())
}
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
index ae32b7a6175c..bce55cbdcc4a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/BackGestureTutorialScreen.kt
@@ -50,7 +50,7 @@ fun BackGestureTutorialScreen(
)
GestureTutorialScreen(
screenConfig = screenConfig,
- gestureUiStateFlow = viewModel.gestureUiState,
+ tutorialStateFlow = viewModel.tutorialState,
motionEventConsumer = {
easterEggGestureViewModel.accept(it)
viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
index 73c54af595d9..284e23e5a288 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt
@@ -18,7 +18,6 @@ package com.android.systemui.touchpad.tutorial.ui.composable
import android.view.MotionEvent
import androidx.activity.compose.BackHandler
-import androidx.annotation.RawRes
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Box
@@ -27,77 +26,21 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.systemui.inputdevice.tutorial.ui.composable.ActionTutorialContent
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState.NotStarted
import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialScreenConfig
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.Finished
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState.NotStarted
-import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import kotlinx.coroutines.flow.Flow
-sealed interface GestureUiState {
- data object NotStarted : GestureUiState
-
- data class Finished(@RawRes val successAnimation: Int) : GestureUiState
-
- data class InProgress(
- val progress: Float = 0f,
- val progressStartMarker: String,
- val progressEndMarker: String,
- ) : GestureUiState
-
- data object Error : GestureUiState
-}
-
-fun GestureState.toGestureUiState(
- progressStartMarker: String,
- progressEndMarker: String,
- successAnimation: Int,
-): GestureUiState {
- return when (this) {
- GestureState.NotStarted -> NotStarted
- is GestureState.InProgress ->
- GestureUiState.InProgress(this.progress, progressStartMarker, progressEndMarker)
- is GestureState.Finished -> GestureUiState.Finished(successAnimation)
- GestureState.Error -> GestureUiState.Error
- }
-}
-
-fun GestureUiState.toTutorialActionState(previousState: TutorialActionState): TutorialActionState {
- return when (this) {
- NotStarted -> TutorialActionState.NotStarted
- is GestureUiState.InProgress -> {
- val inProgress =
- TutorialActionState.InProgress(
- progress = progress,
- startMarker = progressStartMarker,
- endMarker = progressEndMarker,
- )
- if (
- previousState is TutorialActionState.InProgressAfterError ||
- previousState is TutorialActionState.Error
- ) {
- return TutorialActionState.InProgressAfterError(inProgress)
- } else {
- return inProgress
- }
- }
- is Finished -> TutorialActionState.Finished(successAnimation)
- GestureUiState.Error -> TutorialActionState.Error
- }
-}
-
@Composable
fun GestureTutorialScreen(
screenConfig: TutorialScreenConfig,
- gestureUiStateFlow: Flow<GestureUiState>,
+ tutorialStateFlow: Flow<TutorialActionState>,
motionEventConsumer: (MotionEvent) -> Boolean,
easterEggTriggeredFlow: Flow<Boolean>,
onEasterEggFinished: () -> Unit,
@@ -106,25 +49,21 @@ fun GestureTutorialScreen(
) {
BackHandler(onBack = onBack)
val easterEggTriggered by easterEggTriggeredFlow.collectAsStateWithLifecycle(false)
- val gestureState by gestureUiStateFlow.collectAsStateWithLifecycle(NotStarted)
+ val tutorialState by tutorialStateFlow.collectAsStateWithLifecycle(NotStarted)
TouchpadGesturesHandlingBox(
motionEventConsumer,
- gestureState,
+ tutorialState,
easterEggTriggered,
onEasterEggFinished,
) {
- var lastState: TutorialActionState by remember {
- mutableStateOf(TutorialActionState.NotStarted)
- }
- lastState = gestureState.toTutorialActionState(lastState)
- ActionTutorialContent(lastState, onDoneButtonClicked, screenConfig)
+ ActionTutorialContent(tutorialState, onDoneButtonClicked, screenConfig)
}
}
@Composable
private fun TouchpadGesturesHandlingBox(
motionEventConsumer: (MotionEvent) -> Boolean,
- gestureState: GestureUiState,
+ tutorialState: TutorialActionState,
easterEggTriggered: Boolean,
onEasterEggFinished: () -> Unit,
modifier: Modifier = Modifier,
@@ -150,7 +89,7 @@ private fun TouchpadGesturesHandlingBox(
.pointerInteropFilter(
onTouchEvent = { event ->
// FINISHED is the final state so we don't need to process touches anymore
- if (gestureState is Finished) {
+ if (tutorialState is TutorialActionState.Finished) {
false
} else {
motionEventConsumer(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
index 4f1f40dc4c05..4acdb6070200 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/HomeGestureTutorialScreen.kt
@@ -49,7 +49,7 @@ fun HomeGestureTutorialScreen(
)
GestureTutorialScreen(
screenConfig = screenConfig,
- gestureUiStateFlow = viewModel.gestureUiState,
+ tutorialStateFlow = viewModel.tutorialState,
motionEventConsumer = {
easterEggGestureViewModel.accept(it)
viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
index 6c9e26c4b7ea..8dd53a7fb815 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/RecentAppsGestureTutorialScreen.kt
@@ -50,7 +50,7 @@ fun RecentAppsGestureTutorialScreen(
)
GestureTutorialScreen(
screenConfig = screenConfig,
- gestureUiStateFlow = viewModel.gestureUiState,
+ tutorialStateFlow = viewModel.tutorialState,
motionEventConsumer = {
easterEggGestureViewModel.accept(it)
viewModel.handleEvent(it)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
index 8e53669a7841..7a3d4d1ba88a 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/BackGestureScreenViewModel.kt
@@ -17,12 +17,12 @@
package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureDirection
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import com.android.systemui.util.kotlin.pairwiseBy
import kotlinx.coroutines.flow.Flow
@@ -30,21 +30,26 @@ import kotlinx.coroutines.flow.Flow
class BackGestureScreenViewModel(val gestureRecognizer: GestureRecognizerAdapter) :
TouchpadTutorialScreenViewModel {
- override val gestureUiState: Flow<GestureUiState> =
- gestureRecognizer.gestureState.pairwiseBy(GestureState.NotStarted) { previous, current ->
- toGestureUiState(current, previous)
- }
+ override val tutorialState: Flow<TutorialActionState> =
+ gestureRecognizer.gestureState
+ .pairwiseBy(NotStarted) { previous, current ->
+ current to toAnimationProperties(current, previous)
+ }
+ .mapToTutorialState()
override fun handleEvent(event: MotionEvent): Boolean {
return gestureRecognizer.handleTouchpadMotionEvent(event)
}
- private fun toGestureUiState(current: GestureState, previous: GestureState): GestureUiState {
+ private fun toAnimationProperties(
+ current: GestureState,
+ previous: GestureState,
+ ): TutorialAnimationProperties {
val (startMarker, endMarker) =
if (current is InProgress && current.direction == GestureDirection.LEFT) {
"gesture to L" to "end progress L"
} else "gesture to R" to "end progress R"
- return current.toGestureUiState(
+ return TutorialAnimationProperties(
progressStartMarker = startMarker,
progressEndMarker = endMarker,
successAnimation = successAnimation(previous),
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
index 9d6f568fa1b1..c75d44f01e8c 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/HomeGestureScreenViewModel.kt
@@ -17,9 +17,8 @@
package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map
class HomeGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) :
TouchpadTutorialScreenViewModel {
- override val gestureUiState: Flow<GestureUiState> =
- gestureRecognizer.gestureState.map {
- it.toGestureUiState(
- progressStartMarker = "drag with gesture",
- progressEndMarker = "release playback realtime",
- successAnimation = R.raw.trackpad_home_success,
- )
- }
+ override val tutorialState: Flow<TutorialActionState> =
+ gestureRecognizer.gestureState
+ .map {
+ it to
+ TutorialAnimationProperties(
+ progressStartMarker = "drag with gesture",
+ progressEndMarker = "release playback realtime",
+ successAnimation = R.raw.trackpad_home_success,
+ )
+ }
+ .mapToTutorialState()
override fun handleEvent(event: MotionEvent): Boolean {
return gestureRecognizer.handleTouchpadMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
index 97528583277f..9fab5f3641a4 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/RecentAppsGestureScreenViewModel.kt
@@ -17,9 +17,8 @@
package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
import com.android.systemui.res.R
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
-import com.android.systemui.touchpad.tutorial.ui.composable.toGestureUiState
import com.android.systemui.touchpad.tutorial.ui.gesture.handleTouchpadMotionEvent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
@@ -27,14 +26,17 @@ import kotlinx.coroutines.flow.map
class RecentAppsGestureScreenViewModel(private val gestureRecognizer: GestureRecognizerAdapter) :
TouchpadTutorialScreenViewModel {
- override val gestureUiState: Flow<GestureUiState> =
- gestureRecognizer.gestureState.map {
- it.toGestureUiState(
- progressStartMarker = "drag with gesture",
- progressEndMarker = "onPause",
- successAnimation = R.raw.trackpad_recent_apps_success,
- )
- }
+ override val tutorialState: Flow<TutorialActionState> =
+ gestureRecognizer.gestureState
+ .map {
+ it to
+ TutorialAnimationProperties(
+ progressStartMarker = "drag with gesture",
+ progressEndMarker = "onPause",
+ successAnimation = R.raw.trackpad_recent_apps_success,
+ )
+ }
+ .mapToTutorialState()
override fun handleEvent(event: MotionEvent): Boolean {
return gestureRecognizer.handleTouchpadMotionEvent(event)
diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
index 31e953d6643c..3b6e3c76cdeb 100644
--- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/viewmodel/TouchpadTutorialScreenViewModel.kt
@@ -17,11 +17,62 @@
package com.android.systemui.touchpad.tutorial.ui.viewmodel
import android.view.MotionEvent
-import com.android.systemui.touchpad.tutorial.ui.composable.GestureUiState
+import androidx.annotation.RawRes
+import com.android.systemui.inputdevice.tutorial.ui.composable.TutorialActionState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.Finished
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.InProgress
+import com.android.systemui.touchpad.tutorial.ui.gesture.GestureState.NotStarted
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
interface TouchpadTutorialScreenViewModel {
- val gestureUiState: Flow<GestureUiState>
+ val tutorialState: Flow<TutorialActionState>
fun handleEvent(event: MotionEvent): Boolean
}
+
+data class TutorialAnimationProperties(
+ val progressStartMarker: String,
+ val progressEndMarker: String,
+ @RawRes val successAnimation: Int,
+)
+
+fun Flow<Pair<GestureState, TutorialAnimationProperties>>.mapToTutorialState():
+ Flow<TutorialActionState> {
+ return flow<TutorialActionState> {
+ var lastState: TutorialActionState = TutorialActionState.NotStarted
+ collect { (gestureState, animationProperties) ->
+ val newState = gestureState.toTutorialActionState(animationProperties, lastState)
+ lastState = newState
+ emit(newState)
+ }
+ }
+}
+
+fun GestureState.toTutorialActionState(
+ properties: TutorialAnimationProperties,
+ previousState: TutorialActionState,
+): TutorialActionState {
+ return when (this) {
+ NotStarted -> TutorialActionState.NotStarted
+ is InProgress -> {
+ val inProgress =
+ TutorialActionState.InProgress(
+ progress = progress,
+ startMarker = properties.progressStartMarker,
+ endMarker = properties.progressEndMarker,
+ )
+ if (
+ previousState is TutorialActionState.InProgressAfterError ||
+ previousState is TutorialActionState.Error
+ ) {
+ TutorialActionState.InProgressAfterError(inProgress)
+ } else {
+ inProgress
+ }
+ }
+ is Finished -> TutorialActionState.Finished(properties.successAnimation)
+ GestureState.Error -> TutorialActionState.Error
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index 65970978b4ec..7d3966b98782 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -17,8 +17,9 @@ package com.android.systemui.unfold
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
-import android.os.Trace
import com.android.app.tracing.TraceStateLogger
+import com.android.app.tracing.coroutines.TrackTracer
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -29,7 +30,6 @@ import com.android.systemui.util.Utils.isDeviceFoldable
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.plus
/**
@@ -45,7 +45,7 @@ constructor(
@Application applicationScope: CoroutineScope,
@Background private val coroutineContext: CoroutineContext,
private val deviceStateRepository: DeviceStateRepository,
- private val deviceStateManager: DeviceStateManager
+ private val deviceStateManager: DeviceStateManager,
) : CoreStartable {
private val isFoldable: Boolean = isDeviceFoldable(context.resources, deviceStateManager)
@@ -61,7 +61,7 @@ constructor(
bgScope.launch {
foldStateRepository.hingeAngle.collect {
- Trace.traceCounter(Trace.TRACE_TAG_APP, "hingeAngle", it.toInt())
+ TrackTracer.instantForGroup("unfold", "hingeAngle", it.toInt())
}
}
bgScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
index fa1088426351..3b0c8a6b46f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/domain/interactor/VolumeDialogCallbacksInteractor.kt
@@ -46,7 +46,7 @@ class VolumeDialogCallbacksInteractor
constructor(
private val volumeDialogController: VolumeDialogController,
@VolumeDialogPlugin private val coroutineScope: CoroutineScope,
- @Background private val bgHandler: Handler,
+ @Background private val bgHandler: Handler?,
) {
@SuppressLint("SharedFlowCreation") // event-bus needed
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
index 88af210b6a36..940c79c78d76 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -19,7 +19,6 @@ package com.android.systemui.volume.dialog.sliders.dagger
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogOverscrollViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderHapticsViewBinder
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderTouchesViewBinder
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.BindsInstance
import dagger.Subcomponent
@@ -34,8 +33,6 @@ interface VolumeDialogSliderComponent {
fun sliderViewBinder(): VolumeDialogSliderViewBinder
- fun sliderTouchesViewBinder(): VolumeDialogSliderTouchesViewBinder
-
fun sliderHapticsViewBinder(): VolumeDialogSliderHapticsViewBinder
fun overscrollViewBinder(): VolumeDialogOverscrollViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
index 04dc80c45a18..3988acbea7c2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt
@@ -16,7 +16,9 @@
package com.android.systemui.volume.dialog.sliders.domain.interactor
+import com.android.settingslib.volume.shared.model.AudioStream
import com.android.systemui.plugins.VolumeDialogController
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
@@ -27,6 +29,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
@@ -39,8 +43,17 @@ constructor(
@VolumeDialog private val coroutineScope: CoroutineScope,
volumeDialogStateInteractor: VolumeDialogStateInteractor,
private val volumeDialogController: VolumeDialogController,
+ zenModeInteractor: ZenModeInteractor,
) {
+ val isDisabledByZenMode: Flow<Boolean> =
+ if (sliderType is VolumeDialogSliderType.Stream) {
+ zenModeInteractor.activeModesBlockingStream(AudioStream(sliderType.audioStream)).map {
+ it.mainMode != null
+ }
+ } else {
+ flowOf(false)
+ }
val slider: Flow<VolumeDialogStreamModel> =
volumeDialogStateInteractor.volumeDialogState
.mapNotNull {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
deleted file mode 100644
index 4ecac7a81893..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderTouchesViewBinder.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog.sliders.ui
-
-import android.annotation.SuppressLint
-import android.view.View
-import com.android.systemui.res.R
-import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
-import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
-import com.google.android.material.slider.Slider
-import javax.inject.Inject
-
-@VolumeDialogSliderScope
-class VolumeDialogSliderTouchesViewBinder
-@Inject
-constructor(private val viewModel: VolumeDialogSliderInputEventsViewModel) {
-
- @SuppressLint("ClickableViewAccessibility")
- fun bind(view: View) {
- with(view.requireViewById<Slider>(R.id.volume_dialog_slider)) {
- setOnTouchListener { _, event ->
- viewModel.onTouchEvent(event)
- false
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
index 67ffb0602860..3b964fdec1b8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt
@@ -23,6 +23,7 @@ import androidx.dynamicanimation.animation.SpringAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.res.R
import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
+import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderInputEventsViewModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderStateModel
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.google.android.material.slider.Slider
@@ -35,7 +36,10 @@ import kotlinx.coroutines.flow.onEach
@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
@Inject
-constructor(private val viewModel: VolumeDialogSliderViewModel) {
+constructor(
+ private val viewModel: VolumeDialogSliderViewModel,
+ private val inputViewModel: VolumeDialogSliderInputEventsViewModel,
+) {
private val sliderValueProperty =
object : FloatPropertyCompat<Slider>("value") {
@@ -51,16 +55,21 @@ constructor(private val viewModel: VolumeDialogSliderViewModel) {
dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
}
+ @SuppressLint("ClickableViewAccessibility")
fun CoroutineScope.bind(view: View) {
var isInitialUpdate = true
val sliderView: Slider = view.requireViewById(R.id.volume_dialog_slider)
val animation = SpringAnimation(sliderView, sliderValueProperty)
animation.spring = springForce
-
+ sliderView.setOnTouchListener { _, event ->
+ inputViewModel.onTouchEvent(event)
+ false
+ }
sliderView.addOnChangeListener { _, value, fromUser ->
viewModel.setStreamVolume(value.roundToInt(), fromUser)
}
+ viewModel.isDisabledByZenMode.onEach { sliderView.isEnabled = !it }.launchIn(this)
viewModel.state
.onEach {
sliderView.setModel(it, animation, isInitialUpdate)
@@ -82,7 +91,7 @@ constructor(private val viewModel: VolumeDialogSliderViewModel) {
// coerce the current value to the new value range before animating it. This prevents
// animating from the value that is outside of current [valueFrom, valueTo].
value = value.coerceIn(valueFrom, valueTo)
- setTrackIconActiveStart(model.iconRes)
+ trackIconActiveStart = model.icon
if (isInitialUpdate) {
value = model.value
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
index f066b56e7de0..75d427acc05b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt
@@ -71,7 +71,6 @@ constructor(private val viewModel: VolumeDialogSlidersViewModel) {
viewsToAnimate: Array<View>,
) {
with(component.sliderViewBinder()) { bind(sliderContainer) }
- with(component.sliderTouchesViewBinder()) { bind(sliderContainer) }
with(component.sliderHapticsViewBinder()) { bind(sliderContainer) }
with(component.overscrollViewBinder()) { bind(sliderContainer, viewsToAnimate) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
index 5c39b6f9359c..daf4c8275d20 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderIconProvider.kt
@@ -16,13 +16,16 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.drawable.Drawable
import android.media.AudioManager
import androidx.annotation.DrawableRes
-import com.android.settingslib.notification.domain.interactor.NotificationsSoundPolicyInteractor
import com.android.settingslib.volume.domain.interactor.AudioVolumeInteractor
import com.android.settingslib.volume.shared.model.AudioStream
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -31,11 +34,12 @@ import kotlinx.coroutines.flow.flowOf
class VolumeDialogSliderIconProvider
@Inject
constructor(
- private val notificationsSoundPolicyInteractor: NotificationsSoundPolicyInteractor,
+ private val context: Context,
+ private val zenModeInteractor: ZenModeInteractor,
private val audioVolumeInteractor: AudioVolumeInteractor,
) {
- @DrawableRes
+ @SuppressLint("UseCompatLoadingForDrawables")
fun getStreamIcon(
stream: Int,
level: Int,
@@ -43,54 +47,71 @@ constructor(
levelMax: Int,
isMuted: Boolean,
isRoutedToBluetooth: Boolean,
- ): Flow<Int> {
+ ): Flow<Drawable> {
return combine(
- notificationsSoundPolicyInteractor.isZenMuted(AudioStream(stream)),
+ zenModeInteractor.activeModesBlockingStream(AudioStream(stream)),
ringerModeForStream(stream),
- ) { isZenMuted, ringerMode ->
- val isStreamOffline = level == 0 || isMuted
- if (isZenMuted) {
- // TODO(b/372466264) use icon for the corresponding zenmode
- return@combine com.android.internal.R.drawable.ic_qs_dnd
- }
- when (ringerMode?.value) {
- AudioManager.RINGER_MODE_VIBRATE ->
- return@combine R.drawable.ic_volume_ringer_vibrate
- AudioManager.RINGER_MODE_SILENT -> return@combine R.drawable.ic_ring_volume_off
- }
- if (isRoutedToBluetooth) {
- return@combine if (stream == AudioManager.STREAM_VOICE_CALL) {
- R.drawable.ic_volume_bt_sco
- } else {
- if (isStreamOffline) {
- R.drawable.ic_volume_media_bt_mute
- } else {
- R.drawable.ic_volume_media_bt
- }
- }
+ ) { activeModesBlockingStream, ringerMode ->
+ if (activeModesBlockingStream.mainMode?.icon != null) {
+ return@combine activeModesBlockingStream.mainMode.icon.drawable
+ } else {
+ context.getDrawable(
+ getIconRes(
+ stream,
+ level,
+ levelMin,
+ levelMax,
+ isMuted,
+ isRoutedToBluetooth,
+ ringerMode,
+ )
+ )!!
}
+ }
+ }
- return@combine if (isStreamOffline) {
- getMutedIconForStream(stream) ?: getIconForStream(stream)
+ @DrawableRes
+ private fun getIconRes(
+ stream: Int,
+ level: Int,
+ levelMin: Int,
+ levelMax: Int,
+ isMuted: Boolean,
+ isRoutedToBluetooth: Boolean,
+ ringerMode: RingerMode?,
+ ): Int {
+ val isStreamOffline = level == 0 || isMuted
+ when (ringerMode?.value) {
+ AudioManager.RINGER_MODE_VIBRATE -> return R.drawable.ic_volume_ringer_vibrate
+ AudioManager.RINGER_MODE_SILENT -> return R.drawable.ic_ring_volume_off
+ }
+ if (isRoutedToBluetooth) {
+ return if (stream == AudioManager.STREAM_VOICE_CALL) {
+ R.drawable.ic_volume_bt_sco
} else {
- if (level < (levelMax + levelMin) / 2) {
- // This icon is different on TV
- R.drawable.ic_volume_media_low
+ if (isStreamOffline) {
+ R.drawable.ic_volume_media_bt_mute
} else {
- getIconForStream(stream)
+ R.drawable.ic_volume_media_bt
}
}
}
- }
- @DrawableRes
- private fun getMutedIconForStream(stream: Int): Int? {
- return when (stream) {
- AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
- AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
- AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
- AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
- else -> null
+ return if (isStreamOffline) {
+ when (stream) {
+ AudioManager.STREAM_MUSIC -> R.drawable.ic_volume_media_mute
+ AudioManager.STREAM_NOTIFICATION -> R.drawable.ic_volume_ringer_mute
+ AudioManager.STREAM_ALARM -> R.drawable.ic_volume_alarm_mute
+ AudioManager.STREAM_SYSTEM -> R.drawable.ic_volume_system_mute
+ else -> null
+ } ?: getIconForStream(stream)
+ } else {
+ if (level < (levelMax + levelMin) / 2) {
+ // This icon is different on TV
+ R.drawable.ic_volume_media_low
+ } else {
+ getIconForStream(stream)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
index 5750c049082f..8df9e788905c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderStateModel.kt
@@ -16,21 +16,21 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
-import androidx.annotation.DrawableRes
+import android.graphics.drawable.Drawable
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
data class VolumeDialogSliderStateModel(
val minValue: Float,
val maxValue: Float,
val value: Float,
- @DrawableRes val iconRes: Int,
+ val icon: Drawable,
)
-fun VolumeDialogStreamModel.toStateModel(@DrawableRes iconRes: Int): VolumeDialogSliderStateModel {
+fun VolumeDialogStreamModel.toStateModel(icon: Drawable): VolumeDialogSliderStateModel {
return VolumeDialogSliderStateModel(
minValue = levelMin.toFloat(),
value = level.toFloat(),
maxValue = levelMax.toFloat(),
- iconRes = iconRes,
+ icon = icon,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
index 6d8457be1014..d999910675b0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt
@@ -66,12 +66,14 @@ constructor(
private val model: Flow<VolumeDialogStreamModel> =
interactor.slider
.filter {
- val lastVolumeUpdateTime = userVolumeUpdates.value?.timestampMillis ?: 0
+ val currentVolumeUpdate = userVolumeUpdates.value ?: return@filter true
+ val lastVolumeUpdateTime = currentVolumeUpdate.timestampMillis
getTimestampMillis() - lastVolumeUpdateTime > VOLUME_UPDATE_GRACE_PERIOD
}
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
+ val isDisabledByZenMode: Flow<Boolean> = interactor.isDisabledByZenMode
val state: Flow<VolumeDialogSliderStateModel> =
model
.flatMapLatest { streamModel ->
@@ -81,7 +83,7 @@ constructor(
level = level,
levelMin = levelMin,
levelMax = levelMax,
- isMuted = muted,
+ isMuted = muteSupported && muted,
isRoutedToBluetooth = routedToBluetooth,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt
deleted file mode 100644
index e5cf62b91677..000000000000
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/VolumeDialogResources.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.volume.dialog.ui
-
-import android.content.Context
-import android.content.res.Resources
-import com.android.systemui.dagger.qualifiers.UiBackground
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.onConfigChanged
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
-import javax.inject.Inject
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * Provides cached resources [Flow]s that update when the configuration changes.
- *
- * Consume or use [kotlinx.coroutines.flow.first] to get the value.
- */
-@VolumeDialogScope
-class VolumeDialogResources
-@Inject
-constructor(
- @VolumeDialog private val coroutineScope: CoroutineScope,
- @UiBackground private val uiBackgroundContext: CoroutineContext,
- private val context: Context,
- private val configurationController: ConfigurationController,
-) {
-
- val dialogShowDurationMillis: Flow<Long> = configurationResource {
- getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong()
- }
-
- val dialogHideDurationMillis: Flow<Long> = configurationResource {
- getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
- }
-
- private fun <T> configurationResource(get: Resources.() -> T): Flow<T> =
- configurationController.onConfigChanged
- .map { context.resources.get() }
- .onStart { emit(context.resources.get()) }
- .flowOn(uiBackgroundContext)
- .stateIn(coroutineScope, SharingStarted.Eagerly, null)
- .filterNotNull()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
index a3166a9978f4..46d7d5f680ce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/binder/VolumeDialogViewBinder.kt
@@ -17,23 +17,28 @@
package com.android.systemui.volume.dialog.ui.binder
import android.app.Dialog
+import android.content.res.Resources
import android.graphics.Rect
import android.graphics.Region
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.InternalInsetsInfo
+import android.view.WindowInsets
import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.core.view.updatePadding
import com.android.internal.view.RotationPolicy
+import com.android.systemui.common.ui.view.onApplyWindowInsets
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.res.R
import com.android.systemui.util.children
+import com.android.systemui.util.kotlin.awaitCancellationThenDispose
import com.android.systemui.volume.SystemUIInterpolators
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.ringer.ui.binder.VolumeDialogRingerViewBinder
import com.android.systemui.volume.dialog.settings.ui.binder.VolumeDialogSettingsButtonViewBinder
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSlidersViewBinder
-import com.android.systemui.volume.dialog.ui.VolumeDialogResources
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.suspendAnimate
import com.android.systemui.volume.dialog.ui.viewmodel.VolumeDialogViewModel
@@ -42,7 +47,7 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onEach
@@ -56,7 +61,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
class VolumeDialogViewBinder
@Inject
constructor(
- private val volumeResources: VolumeDialogResources,
+ @Main resources: Resources,
private val viewModel: VolumeDialogViewModel,
private val jankListenerFactory: JankListenerFactory,
private val tracer: VolumeTracer,
@@ -65,7 +70,14 @@ constructor(
private val settingsButtonViewBinder: VolumeDialogSettingsButtonViewBinder,
) {
+ private val dialogShowAnimationDurationMs =
+ resources.getInteger(R.integer.config_dialogShowAnimationDurationMs).toLong()
+ private val dialogHideAnimationDurationMs =
+ resources.getInteger(R.integer.config_dialogHideAnimationDurationMs).toLong()
+
fun CoroutineScope.bind(dialog: Dialog) {
+ val insets: MutableStateFlow<WindowInsets> =
+ MutableStateFlow(WindowInsets.Builder().build())
// Root view of the Volume Dialog.
val root: MotionLayout = dialog.requireViewById(R.id.volume_dialog_root)
root.alpha = 0f
@@ -83,6 +95,22 @@ constructor(
launch { root.viewTreeObserver.computeInternalInsetsListener(root) }
+ launch {
+ root
+ .onApplyWindowInsets { v, newInsets ->
+ val insetsValues = newInsets.getInsets(WindowInsets.Type.displayCutout())
+ v.updatePadding(
+ left = insetsValues.left,
+ top = insetsValues.top,
+ right = insetsValues.right,
+ bottom = insetsValues.bottom,
+ )
+ insets.value = newInsets
+ WindowInsets.CONSUMED
+ }
+ .awaitCancellationThenDispose()
+ }
+
with(volumeDialogRingerViewBinder) { bind(root) }
with(slidersViewBinder) { bind(root) }
with(settingsButtonViewBinder) { bind(root) }
@@ -98,13 +126,15 @@ constructor(
when (it) {
is VolumeDialogVisibilityModel.Visible -> {
tracer.traceVisibilityEnd(it)
- calculateTranslationX(view)?.let(view::setTranslationX)
- view.animateShow(volumeResources.dialogShowDurationMillis.first())
+ view.animateShow(
+ duration = dialogShowAnimationDurationMs,
+ translationX = calculateTranslationX(view),
+ )
}
is VolumeDialogVisibilityModel.Dismissed -> {
tracer.traceVisibilityEnd(it)
view.animateHide(
- duration = volumeResources.dialogHideDurationMillis.first(),
+ duration = dialogHideAnimationDurationMs,
translationX = calculateTranslationX(view),
)
dialog.dismiss()
@@ -129,24 +159,15 @@ constructor(
}
}
- private suspend fun View.animateShow(duration: Long) {
+ private suspend fun View.animateShow(duration: Long, translationX: Float?) {
+ translationX?.let { setTranslationX(translationX) }
+ alpha = 0f
animate()
.alpha(1f)
.translationX(0f)
.setDuration(duration)
.setInterpolator(SystemUIInterpolators.LogDecelerateInterpolator())
.suspendAnimate(jankListenerFactory.show(this, duration))
- /* TODO(b/369993851)
- .withEndAction(Runnable {
- if (!Prefs.getBoolean(mContext, Prefs.Key.TOUCHED_RINGER_TOGGLE, false)) {
- if (mRingerIcon != null) {
- mRingerIcon.postOnAnimationDelayed(
- getSinglePressFor(mRingerIcon), 1500
- )
- }
- }
- })
- */
}
private suspend fun View.animateHide(duration: Long, translationX: Float?) {
@@ -155,22 +176,7 @@ constructor(
.alpha(0f)
.setDuration(duration)
.setInterpolator(SystemUIInterpolators.LogAccelerateInterpolator())
- /* TODO(b/369993851)
- .withEndAction(
- Runnable {
- mHandler.postDelayed(
- Runnable {
- hideRingerDrawer()
-
- },
- 50
- )
- }
- )
- */
- if (translationX != null) {
- animator.translationX(translationX)
- }
+ translationX?.let { animator.translationX(it) }
animator.suspendAnimate(jankListenerFactory.dismiss(this, duration))
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
index b20dffb8ac33..7a6ede4c8b9c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt
@@ -44,9 +44,9 @@ class VolumeDialogViewModel
@Inject
constructor(
private val context: Context,
- private val dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
+ dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
- private val volumeDialogStateInteractor: VolumeDialogStateInteractor,
+ volumeDialogStateInteractor: VolumeDialogStateInteractor,
devicePostureController: DevicePostureController,
configurationController: ConfigurationController,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index cec3d1eb86f0..5b8d9b045475 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -18,9 +18,6 @@ package com.android.systemui.volume.panel.component.volume.slider.ui.viewmodel
import android.content.Context
import android.media.AudioManager
-import android.media.AudioManager.STREAM_ALARM
-import android.media.AudioManager.STREAM_MUSIC
-import android.media.AudioManager.STREAM_NOTIFICATION
import android.util.Log
import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
@@ -34,8 +31,6 @@ import com.android.systemui.haptics.slider.compose.ui.SliderHapticsViewModel
import com.android.systemui.modes.shared.ModesUiIcons
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
-import com.android.systemui.statusbar.policy.domain.model.ActiveZenModes
-import com.android.systemui.util.kotlin.combine
import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
@@ -43,12 +38,15 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlin.math.roundToInt
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
@@ -101,48 +99,16 @@ constructor(
)
override val slider: StateFlow<SliderState> =
- if (ModesUiIcons.isEnabled) {
- combine(
- audioVolumeInteractor.getAudioStream(audioStream),
- audioVolumeInteractor.canChangeVolume(audioStream),
- audioVolumeInteractor.ringerMode,
- zenModeInteractor.activeModesBlockingEverything,
- zenModeInteractor.activeModesBlockingAlarms,
- zenModeInteractor.activeModesBlockingMedia,
- ) {
- model,
- isEnabled,
- ringerMode,
- modesBlockingEverything,
- modesBlockingAlarms,
- modesBlockingMedia ->
- volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
- model.toState(
- isEnabled,
- ringerMode,
- getStreamDisabledMessage(
- modesBlockingEverything,
- modesBlockingAlarms,
- modesBlockingMedia,
- ),
- )
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
- } else {
- combine(
- audioVolumeInteractor.getAudioStream(audioStream),
- audioVolumeInteractor.canChangeVolume(audioStream),
- audioVolumeInteractor.ringerMode,
- ) { model, isEnabled, ringerMode ->
- volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
- model.toState(
- isEnabled,
- ringerMode,
- getStreamDisabledMessageWithoutModes(audioStream),
- )
- }
- .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
- }
+ combine(
+ audioVolumeInteractor.getAudioStream(audioStream),
+ audioVolumeInteractor.canChangeVolume(audioStream),
+ audioVolumeInteractor.ringerMode,
+ streamDisabledMessage(),
+ ) { model, isEnabled, ringerMode, streamDisabledMessage ->
+ volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
+ model.toState(isEnabled, ringerMode, streamDisabledMessage)
+ }
+ .stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
init {
volumeChanges
@@ -229,40 +195,32 @@ constructor(
)
}
- private fun getStreamDisabledMessage(
- blockingEverything: ActiveZenModes,
- blockingAlarms: ActiveZenModes,
- blockingMedia: ActiveZenModes,
- ): String {
- // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING.
- // In fact, VOICE_CALL should not be affected by interruption filtering at all.
- return if (audioStream.value == STREAM_NOTIFICATION) {
- context.getString(R.string.stream_notification_unavailable)
- } else {
- val blockingModeName =
- when {
- blockingEverything.mainMode != null -> blockingEverything.mainMode.name
- audioStream.value == STREAM_ALARM -> blockingAlarms.mainMode?.name
- audioStream.value == STREAM_MUSIC -> blockingMedia.mainMode?.name
- else -> null
- }
-
- if (blockingModeName != null) {
- context.getString(R.string.stream_unavailable_by_modes, blockingModeName)
+ // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING.
+ // In fact, VOICE_CALL should not be affected by interruption filtering at all.
+ private fun streamDisabledMessage(): Flow<String> {
+ return if (ModesUiIcons.isEnabled) {
+ if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
+ flowOf(context.getString(R.string.stream_notification_unavailable))
} else {
- // Should not actually be visible, but as a catch-all.
- context.getString(R.string.stream_unavailable_by_unknown)
+ if (zenModeInteractor.canBeBlockedByZenMode(audioStream)) {
+ zenModeInteractor.activeModesBlockingStream(audioStream).map { blockingZenModes
+ ->
+ blockingZenModes.mainMode?.name?.let {
+ context.getString(R.string.stream_unavailable_by_modes, it)
+ } ?: context.getString(R.string.stream_unavailable_by_unknown)
+ }
+ } else {
+ flowOf(context.getString(R.string.stream_unavailable_by_unknown))
+ }
}
- }
- }
-
- private fun getStreamDisabledMessageWithoutModes(audioStream: AudioStream): String {
- // TODO: b/372213356 - Figure out the correct messages for VOICE_CALL and RING.
- // In fact, VOICE_CALL should not be affected by interruption filtering at all.
- return if (audioStream.value == STREAM_NOTIFICATION) {
- context.getString(R.string.stream_notification_unavailable)
} else {
- context.getString(R.string.stream_alarm_unavailable)
+ flowOf(
+ if (audioStream.value == AudioManager.STREAM_NOTIFICATION) {
+ context.getString(R.string.stream_notification_unavailable)
+ } else {
+ context.getString(R.string.stream_alarm_unavailable)
+ }
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index 387cc084f9cd..1320223cabf3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -33,7 +33,6 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModelFactory
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 7a3089f33276..77c40a1e8eef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.data.repository
+import android.animation.AnimationHandler
import android.animation.Animator
import android.animation.ValueAnimator
import android.platform.test.annotations.EnableFlags
@@ -36,6 +37,7 @@ import com.android.systemui.keyguard.shared.model.TransitionInfo
import com.android.systemui.keyguard.shared.model.TransitionModeOnCanceled
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.util.FrameCallbackProvider
import com.android.systemui.keyguard.util.KeyguardTransitionRunner
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
@@ -52,9 +54,12 @@ import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,13 +75,29 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardTransitionRepository
private lateinit var runner: KeyguardTransitionRunner
+ private lateinit var callbackProvider: FrameCallbackProvider
private val animatorListener = mock<Animator.AnimatorListener>()
@Before
fun setUp() {
underTest = KeyguardTransitionRepositoryImpl(Dispatchers.Main)
- runner = KeyguardTransitionRunner(underTest)
+ runBlocking {
+ callbackProvider = FrameCallbackProvider(testScope.backgroundScope)
+ withContext(Dispatchers.Main) {
+ // AnimationHandler uses ThreadLocal storage, and ValueAnimators MUST start from
+ // main thread
+ AnimationHandler.getInstance().setProvider(callbackProvider)
+ }
+ runner = KeyguardTransitionRunner(callbackProvider.frames, underTest)
+ }
+ }
+
+ @After
+ fun tearDown() {
+ runBlocking {
+ withContext(Dispatchers.Main) { AnimationHandler.getInstance().setProvider(null) }
+ }
}
@Test
@@ -84,13 +105,11 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
testScope.runTest {
val steps = mutableListOf<TransitionStep>()
val job = underTest.transition(AOD, LOCKSCREEN).onEach { steps.add(it) }.launchIn(this)
-
runner.startTransition(
this,
TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
maxFrames = 100,
)
-
assertSteps(steps, listWithStep(BigDecimal(.1)), AOD, LOCKSCREEN)
job.cancel()
}
@@ -119,12 +138,12 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
),
)
- val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
- assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.2))
+ assertSteps(steps.subList(0, 5), firstTransitionSteps, AOD, LOCKSCREEN)
- // Second transition starts from .1 (LAST_VALUE)
- val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.1))
- assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+ // Second transition starts from .2 (LAST_VALUE)
+ val secondTransitionSteps = listWithStep(step = BigDecimal(.1), start = BigDecimal(.2))
+ assertSteps(steps.subList(5, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
job.cancel()
job2.cancel()
@@ -154,12 +173,12 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
),
)
- val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.1))
- assertSteps(steps.subList(0, 4), firstTransitionSteps, AOD, LOCKSCREEN)
+ val firstTransitionSteps = listWithStep(step = BigDecimal(.1), stop = BigDecimal(.2))
+ assertSteps(steps.subList(0, 5), firstTransitionSteps, AOD, LOCKSCREEN)
// Second transition starts from 0 (RESET)
val secondTransitionSteps = listWithStep(start = BigDecimal(0), step = BigDecimal(.1))
- assertSteps(steps.subList(4, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
+ assertSteps(steps.subList(5, steps.size), secondTransitionSteps, LOCKSCREEN, AOD)
job.cancel()
job2.cancel()
@@ -173,7 +192,7 @@ class KeyguardTransitionRepositoryTest : SysuiTestCase() {
runner.startTransition(
this,
TransitionInfo(OWNER_NAME, AOD, LOCKSCREEN, getAnimator()),
- maxFrames = 3,
+ maxFrames = 2,
)
// Now start 2nd transition, which will interrupt the first
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 2aa300df4f7c..2aa300df4f7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
new file mode 100644
index 000000000000..a192446e535b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentManagerTest.kt
@@ -0,0 +1,819 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.dialog
+
+import android.content.Intent
+import android.os.Handler
+import android.os.fakeExecutorHandler
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import android.telephony.telephonyManager
+import android.testing.TestableLooper.RunWithLooper
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.Button
+import android.widget.LinearLayout
+import android.widget.Switch
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import androidx.test.annotation.UiThreadTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.wifi.WifiEnterpriseRestrictionUtils
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogTransitionAnimator
+import com.android.systemui.flags.setFlagValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.WifiEntry
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.MockitoSession
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+@UiThreadTest
+class InternetDetailsContentManagerTest : SysuiTestCase() {
+ private val kosmos = Kosmos()
+ private val handler: Handler = kosmos.fakeExecutorHandler
+ private val scope: CoroutineScope = mock<CoroutineScope>()
+ private val telephonyManager: TelephonyManager = kosmos.telephonyManager
+ private val internetWifiEntry: WifiEntry = mock<WifiEntry>()
+ private val wifiEntries: List<WifiEntry> = mock<List<WifiEntry>>()
+ private val internetAdapter = mock<InternetAdapter>()
+ private val internetDetailsContentController: InternetDetailsContentController =
+ mock<InternetDetailsContentController>()
+ private val keyguard: KeyguardStateController = mock<KeyguardStateController>()
+ private val dialogTransitionAnimator: DialogTransitionAnimator =
+ mock<DialogTransitionAnimator>()
+ private val bgExecutor = FakeExecutor(FakeSystemClock())
+ private lateinit var internetDetailsContentManager: InternetDetailsContentManager
+ private var subTitle: View? = null
+ private var ethernet: LinearLayout? = null
+ private var mobileDataLayout: LinearLayout? = null
+ private var mobileToggleSwitch: Switch? = null
+ private var wifiToggle: LinearLayout? = null
+ private var wifiToggleSwitch: Switch? = null
+ private var wifiToggleSummary: TextView? = null
+ private var connectedWifi: LinearLayout? = null
+ private var wifiList: RecyclerView? = null
+ private var seeAll: LinearLayout? = null
+ private var wifiScanNotify: LinearLayout? = null
+ private var airplaneModeSummaryText: TextView? = null
+ private var mockitoSession: MockitoSession? = null
+ private var sharedWifiButton: Button? = null
+ private lateinit var contentView: View
+
+ @Before
+ fun setUp() {
+ // TODO: b/377388104 enable this flag after integrating with details view.
+ mSetFlagsRule.setFlagValue(Flags.FLAG_QS_TILE_DETAILED_VIEW, false)
+ whenever(telephonyManager.createForSubscriptionId(ArgumentMatchers.anyInt()))
+ .thenReturn(telephonyManager)
+ whenever(internetWifiEntry.title).thenReturn(WIFI_TITLE)
+ whenever(internetWifiEntry.getSummary(false)).thenReturn(WIFI_SUMMARY)
+ whenever(internetWifiEntry.isDefaultNetwork).thenReturn(true)
+ whenever(internetWifiEntry.hasInternetAccess()).thenReturn(true)
+ whenever(wifiEntries.size).thenReturn(1)
+ whenever(internetDetailsContentController.getDialogTitleText()).thenReturn(TITLE)
+ whenever(internetDetailsContentController.getMobileNetworkTitle(ArgumentMatchers.anyInt()))
+ .thenReturn(MOBILE_NETWORK_TITLE)
+ whenever(
+ internetDetailsContentController.getMobileNetworkSummary(ArgumentMatchers.anyInt())
+ )
+ .thenReturn(MOBILE_NETWORK_SUMMARY)
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.activeAutoSwitchNonDdsSubId)
+ .thenReturn(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ mockitoSession =
+ ExtendedMockito.mockitoSession()
+ .spyStatic(WifiEnterpriseRestrictionUtils::class.java)
+ .startMocking()
+ whenever(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true)
+ createView()
+ }
+
+ private fun createView() {
+ contentView =
+ LayoutInflater.from(mContext).inflate(R.layout.internet_connectivity_dialog, null)
+ internetDetailsContentManager =
+ InternetDetailsContentManager(
+ internetDetailsContentController,
+ canConfigMobileData = true,
+ canConfigWifi = true,
+ coroutineScope = scope,
+ context = mContext,
+ internetDialog = null,
+ uiEventLogger = mock<UiEventLogger>(),
+ dialogTransitionAnimator = dialogTransitionAnimator,
+ handler = handler,
+ backgroundExecutor = bgExecutor,
+ keyguard = keyguard,
+ )
+
+ internetDetailsContentManager.bind(contentView)
+ internetDetailsContentManager.adapter = internetAdapter
+ internetDetailsContentManager.connectedWifiEntry = internetWifiEntry
+ internetDetailsContentManager.wifiEntriesCount = wifiEntries.size
+
+ subTitle = contentView.requireViewById(R.id.internet_dialog_subtitle)
+ ethernet = contentView.requireViewById(R.id.ethernet_layout)
+ mobileDataLayout = contentView.requireViewById(R.id.mobile_network_layout)
+ mobileToggleSwitch = contentView.requireViewById(R.id.mobile_toggle)
+ wifiToggle = contentView.requireViewById(R.id.turn_on_wifi_layout)
+ wifiToggleSwitch = contentView.requireViewById(R.id.wifi_toggle)
+ wifiToggleSummary = contentView.requireViewById(R.id.wifi_toggle_summary)
+ connectedWifi = contentView.requireViewById(R.id.wifi_connected_layout)
+ wifiList = contentView.requireViewById(R.id.wifi_list_layout)
+ seeAll = contentView.requireViewById(R.id.see_all_layout)
+ wifiScanNotify = contentView.requireViewById(R.id.wifi_scan_notify_layout)
+ airplaneModeSummaryText = contentView.requireViewById(R.id.airplane_mode_summary)
+ sharedWifiButton = contentView.requireViewById(R.id.share_wifi_button)
+ }
+
+ @After
+ fun tearDown() {
+ internetDetailsContentManager.unBind()
+ mockitoSession!!.finishMocking()
+ }
+
+ @Test
+ fun createView_setAccessibilityPaneTitleToQuickSettings() {
+ assertThat(contentView.accessibilityPaneTitle)
+ .isEqualTo(mContext.getText(R.string.accessibility_desc_quick_settings))
+ }
+
+ @Test
+ fun hideWifiViews_WifiViewsGone() {
+ internetDetailsContentManager.hideWifiViews()
+
+ assertThat(internetDetailsContentManager.isProgressBarVisible).isFalse()
+ assertThat(wifiToggle!!.visibility).isEqualTo(View.GONE)
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ assertThat(wifiList!!.visibility).isEqualTo(View.GONE)
+ assertThat(seeAll!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun updateContent_withApmOn_internetDialogSubTitleGone() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_withApmOff_internetDialogSubTitleVisible() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(subTitle!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOffAndHasEthernet_showEthernet() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.hasEthernet()).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(ethernet!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOffAndNoEthernet_hideEthernet() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.hasEthernet()).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(ethernet!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnAndHasEthernet_showEthernet() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.hasEthernet()).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(ethernet!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnAndNoEthernet_hideEthernet() {
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.hasEthernet()).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(ethernet!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOffAndNotCarrierNetwork_mobileDataLayoutGone() {
+ // Mobile network should be gone if the list of active subscriptionId is null.
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(false)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileDataLayout!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutVisible() {
+ // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileDataLayout!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutGone() {
+ // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileDataLayout!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnAndNoCarrierNetwork_mobileDataLayoutGone() {
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(false)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileDataLayout!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnAndWifiOnHasCarrierNetwork_showAirplaneSummary() {
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ internetDetailsContentManager.connectedWifiEntry = null
+ whenever(internetDetailsContentController.activeNetworkIsCellular()).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileDataLayout!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(airplaneModeSummaryText!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOffAndWifiOnHasCarrierNetwork_notShowApmSummary() {
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ internetDetailsContentManager.connectedWifiEntry = null
+ whenever(internetDetailsContentController.activeNetworkIsCellular()).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(airplaneModeSummaryText!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOffAndHasCarrierNetwork_notShowApmSummary() {
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(airplaneModeSummaryText!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_apmOnAndNoCarrierNetwork_notShowApmSummary() {
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(false)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(true)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(airplaneModeSummaryText!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_mobileDataIsEnabled_checkMobileDataSwitch() {
+ whenever(internetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true)
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isMobileDataEnabled).thenReturn(true)
+ mobileToggleSwitch!!.isChecked = false
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileToggleSwitch!!.isChecked).isTrue()
+ }
+ }
+
+ @Test
+ fun updateContent_mobileDataIsNotChanged_checkMobileDataSwitch() {
+ whenever(internetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true)
+ whenever(internetDetailsContentController.isCarrierNetworkActive).thenReturn(true)
+ whenever(internetDetailsContentController.isMobileDataEnabled).thenReturn(false)
+ mobileToggleSwitch!!.isChecked = false
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(mobileToggleSwitch!!.isChecked).isFalse()
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndHasInternetWifi_showConnectedWifi() {
+ whenever(internetDetailsContentController.activeAutoSwitchNonDdsSubId).thenReturn(1)
+ whenever(internetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true)
+
+ // The preconditions WiFi ON and Internet WiFi are already in setUp()
+ whenever(internetDetailsContentController.activeNetworkIsCellular()).thenReturn(false)
+
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.VISIBLE)
+ val secondaryLayout =
+ contentView.requireViewById<LinearLayout>(R.id.secondary_mobile_network_layout)
+ assertThat(secondaryLayout.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndNoConnectedWifi_hideConnectedWifi() {
+ // The precondition WiFi ON is already in setUp()
+ internetDetailsContentManager.connectedWifiEntry = null
+ whenever(internetDetailsContentController.activeNetworkIsCellular()).thenReturn(false)
+
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndNoWifiEntry_showWifiListAndSeeAllArea() {
+ // The precondition WiFi ON is already in setUp()
+ internetDetailsContentManager.connectedWifiEntry = null
+ internetDetailsContentManager.wifiEntriesCount = 0
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ // Show a blank block to fix the details content height even if there is no WiFi list
+ assertThat(wifiList!!.visibility).isEqualTo(View.VISIBLE)
+ verify(internetAdapter).setMaxEntriesCount(3)
+ assertThat(seeAll!!.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndOneWifiEntry_showWifiListAndSeeAllArea() {
+ // The precondition WiFi ON is already in setUp()
+ internetDetailsContentManager.connectedWifiEntry = null
+ internetDetailsContentManager.wifiEntriesCount = 1
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ // Show a blank block to fix the details content height even if there is no WiFi list
+ assertThat(wifiList!!.visibility).isEqualTo(View.VISIBLE)
+ verify(internetAdapter).setMaxEntriesCount(3)
+ assertThat(seeAll!!.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ internetDetailsContentManager.wifiEntriesCount = 0
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.VISIBLE)
+ // Show a blank block to fix the details content height even if there is no WiFi list
+ assertThat(wifiList!!.visibility).isEqualTo(View.VISIBLE)
+ verify(internetAdapter).setMaxEntriesCount(2)
+ assertThat(seeAll!!.visibility).isEqualTo(View.INVISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndHasMaxWifiList_showWifiListAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ internetDetailsContentManager.connectedWifiEntry = null
+ internetDetailsContentManager.wifiEntriesCount =
+ InternetDetailsContentController.MAX_WIFI_ENTRY_COUNT
+ internetDetailsContentManager.hasMoreWifiEntries = true
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ assertThat(wifiList!!.visibility).isEqualTo(View.VISIBLE)
+ verify(internetAdapter).setMaxEntriesCount(3)
+ assertThat(seeAll!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOnAndHasBothWifiEntry_showBothWifiEntryAndSeeAll() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ internetDetailsContentManager.wifiEntriesCount =
+ InternetDetailsContentController.MAX_WIFI_ENTRY_COUNT - 1
+ internetDetailsContentManager.hasMoreWifiEntries = true
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(wifiList!!.visibility).isEqualTo(View.VISIBLE)
+ verify(internetAdapter).setMaxEntriesCount(2)
+ assertThat(seeAll!!.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ @Test
+ fun updateContent_deviceLockedAndNoConnectedWifi_showWifiToggle() {
+ // The preconditions WiFi entries are already in setUp()
+ whenever(internetDetailsContentController.isDeviceLocked).thenReturn(true)
+ internetDetailsContentManager.connectedWifiEntry = null
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ // Show WiFi Toggle without background
+ assertThat(wifiToggle!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(wifiToggle!!.background).isNull()
+ // Hide Wi-Fi networks and See all
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ assertThat(wifiList!!.visibility).isEqualTo(View.GONE)
+ assertThat(seeAll!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_deviceLockedAndHasConnectedWifi_showWifiToggleWithBackground() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ whenever(internetDetailsContentController.isDeviceLocked).thenReturn(true)
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ // Show WiFi Toggle with highlight background
+ assertThat(wifiToggle!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(wifiToggle!!.background).isNotNull()
+ // Hide Wi-Fi networks and See all
+ assertThat(connectedWifi!!.visibility).isEqualTo(View.GONE)
+ assertThat(wifiList!!.visibility).isEqualTo(View.GONE)
+ assertThat(seeAll!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_disallowChangeWifiState_disableWifiSwitch() {
+ whenever(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext))
+ .thenReturn(false)
+ createView()
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ // Disable Wi-Fi switch and show restriction message in summary.
+ assertThat(wifiToggleSwitch!!.isEnabled).isFalse()
+ assertThat(wifiToggleSummary!!.visibility).isEqualTo(View.VISIBLE)
+ assertThat(wifiToggleSummary!!.text.length).isNotEqualTo(0)
+ }
+ }
+
+ @Test
+ fun updateContent_allowChangeWifiState_enableWifiSwitch() {
+ whenever(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true)
+ createView()
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ // Enable Wi-Fi switch and hide restriction message in summary.
+ assertThat(wifiToggleSwitch!!.isEnabled).isTrue()
+ assertThat(wifiToggleSummary!!.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_showSecondaryDataSub() {
+ whenever(internetDetailsContentController.activeAutoSwitchNonDdsSubId).thenReturn(1)
+ whenever(internetDetailsContentController.hasActiveSubIdOnDds()).thenReturn(true)
+ whenever(internetDetailsContentController.isAirplaneModeEnabled).thenReturn(false)
+
+ clearInvocations(internetDetailsContentController)
+ internetDetailsContentManager.updateContent(true)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ val primaryLayout =
+ contentView.requireViewById<LinearLayout>(R.id.mobile_network_layout)
+ val secondaryLayout =
+ contentView.requireViewById<LinearLayout>(R.id.secondary_mobile_network_layout)
+
+ verify(internetDetailsContentController).getMobileNetworkSummary(1)
+ assertThat(primaryLayout.background).isNotEqualTo(secondaryLayout.background)
+ }
+ }
+
+ @Test
+ fun updateContent_wifiOn_hideWifiScanNotify() {
+ // The preconditions WiFi ON and WiFi entries are already in setUp()
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun updateContent_wifiOffAndWifiScanOff_hideWifiScanNotify() {
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.isWifiScanEnabled).thenReturn(false)
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun updateContent_wifiOffAndWifiScanOnAndDeviceLocked_hideWifiScanNotify() {
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.isWifiScanEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.isDeviceLocked).thenReturn(true)
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.GONE)
+ }
+
+ @Test
+ fun updateContent_wifiOffAndWifiScanOnAndDeviceUnlocked_showWifiScanNotify() {
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(false)
+ whenever(internetDetailsContentController.isWifiScanEnabled).thenReturn(true)
+ whenever(internetDetailsContentController.isDeviceLocked).thenReturn(false)
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiScanNotify!!.visibility).isEqualTo(View.VISIBLE)
+ val wifiScanNotifyText =
+ contentView.requireViewById<TextView>(R.id.wifi_scan_notify_text)
+ assertThat(wifiScanNotifyText.text.length).isNotEqualTo(0)
+ assertThat(wifiScanNotifyText.movementMethod).isNotNull()
+ }
+ }
+
+ @Test
+ fun updateContent_wifiIsDisabled_uncheckWifiSwitch() {
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(false)
+ wifiToggleSwitch!!.isChecked = true
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiToggleSwitch!!.isChecked).isFalse()
+ }
+ }
+
+ @Test
+ @Throws(Exception::class)
+ fun updateContent_wifiIsEnabled_checkWifiSwitch() {
+ whenever(internetDetailsContentController.isWifiEnabled).thenReturn(true)
+ wifiToggleSwitch!!.isChecked = false
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(wifiToggleSwitch!!.isChecked).isTrue()
+ }
+ }
+
+ @Test
+ fun onClickSeeMoreButton_clickSeeAll_verifyLaunchNetworkSetting() {
+ seeAll!!.performClick()
+
+ verify(internetDetailsContentController)
+ .launchNetworkSetting(contentView.requireViewById(R.id.see_all_layout))
+ }
+
+ @Test
+ fun onWifiScan_isScanTrue_setProgressBarVisibleTrue() {
+ internetDetailsContentManager.isProgressBarVisible = false
+
+ internetDetailsContentManager.internetDetailsCallback.onWifiScan(true)
+
+ assertThat(internetDetailsContentManager.isProgressBarVisible).isTrue()
+ }
+
+ @Test
+ fun onWifiScan_isScanFalse_setProgressBarVisibleFalse() {
+ internetDetailsContentManager.isProgressBarVisible = true
+
+ internetDetailsContentManager.internetDetailsCallback.onWifiScan(false)
+
+ assertThat(internetDetailsContentManager.isProgressBarVisible).isFalse()
+ }
+
+ @Test
+ fun updateContent_shareWifiIntentNull_hideButton() {
+ whenever(
+ internetDetailsContentController.getConfiguratorQrCodeGeneratorIntentOrNull(
+ ArgumentMatchers.any()
+ )
+ )
+ .thenReturn(null)
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(sharedWifiButton?.visibility).isEqualTo(View.GONE)
+ }
+ }
+
+ @Test
+ fun updateContent_shareWifiShareable_showButton() {
+ whenever(
+ internetDetailsContentController.getConfiguratorQrCodeGeneratorIntentOrNull(
+ ArgumentMatchers.any()
+ )
+ )
+ .thenReturn(Intent())
+ internetDetailsContentManager.updateContent(false)
+ bgExecutor.runAllReady()
+
+ internetDetailsContentManager.internetContentData.observe(
+ internetDetailsContentManager.lifecycleOwner!!
+ ) {
+ assertThat(sharedWifiButton?.visibility).isEqualTo(View.VISIBLE)
+ }
+ }
+
+ companion object {
+ private const val TITLE = "Internet"
+ private const val MOBILE_NETWORK_TITLE = "Mobile Title"
+ private const val MOBILE_NETWORK_SUMMARY = "Mobile Summary"
+ private const val WIFI_TITLE = "Connected Wi-Fi Title"
+ private const val WIFI_SUMMARY = "Connected Wi-Fi Summary"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 2c37f510a45c..77bac59b9dcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -15,7 +15,6 @@
*/
package com.android.systemui.statusbar.notification.collection.coordinator
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -33,15 +32,14 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStackC
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.util.mockito.withArgCaptor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
@@ -82,9 +80,9 @@ class StackCoordinatorTest : SysuiTestCase() {
sensitiveNotificationProtectionController,
)
coordinator.attach(pipeline)
- val captor = argumentCaptor<OnAfterRenderListListener>()
- verify(pipeline).addOnAfterRenderListListener(captor.capture())
- afterRenderListListener = captor.lastValue
+ afterRenderListListener = withArgCaptor {
+ verify(pipeline).addOnAfterRenderListListener(capture())
+ }
}
@Test
@@ -94,93 +92,9 @@ class StackCoordinatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
- fun testSetRenderedListOnInteractor_footerFlagOn() {
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(renderListInteractor).setRenderedList(eq(listOf(entry)))
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
fun testSetNotificationStats_clearableAlerting() {
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController)
- .setNotifStats(
- NotifStats(
- 1,
- hasNonClearableAlertingNotifs = false,
- hasClearableAlertingNotifs = true,
- hasNonClearableSilentNotifs = false,
- hasClearableSilentNotifs = false,
- )
- )
- verifyNoMoreInteractions(activeNotificationsInteractor)
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
- fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() {
- whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
- whenever(section.bucket).thenReturn(BUCKET_ALERTING)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController)
- .setNotifStats(
- NotifStats(
- 1,
- hasNonClearableAlertingNotifs = true,
- hasClearableAlertingNotifs = false,
- hasNonClearableSilentNotifs = false,
- hasClearableSilentNotifs = false,
- )
- )
- verifyNoMoreInteractions(activeNotificationsInteractor)
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- fun testSetNotificationStats_clearableSilent() {
- whenever(section.bucket).thenReturn(BUCKET_SILENT)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController)
- .setNotifStats(
- NotifStats(
- 1,
- hasNonClearableAlertingNotifs = false,
- hasClearableAlertingNotifs = false,
- hasNonClearableSilentNotifs = false,
- hasClearableSilentNotifs = true,
- )
- )
- verifyNoMoreInteractions(activeNotificationsInteractor)
- }
-
- @Test
- @DisableFlags(FooterViewRefactor.FLAG_NAME)
- @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
- fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() {
- whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
- whenever(section.bucket).thenReturn(BUCKET_SILENT)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
- verify(stackController)
- .setNotifStats(
- NotifStats(
- 1,
- hasNonClearableAlertingNotifs = false,
- hasClearableAlertingNotifs = false,
- hasNonClearableSilentNotifs = true,
- hasClearableSilentNotifs = false,
- )
- )
- verifyNoMoreInteractions(activeNotificationsInteractor)
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
- fun testSetNotificationStats_footerFlagOn_clearableAlerting() {
- whenever(section.bucket).thenReturn(BUCKET_ALERTING)
- afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
.setNotifStats(
NotifStats(
@@ -195,12 +109,8 @@ class StackCoordinatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(
- FooterViewRefactor.FLAG_NAME,
- FLAG_SCREENSHARE_NOTIFICATION_HIDING,
- FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
- )
- fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableAlerting() {
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun testSetNotificationStats_isSensitiveStateActive_nonClearableAlerting() {
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
@@ -218,8 +128,7 @@ class StackCoordinatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
- fun testSetNotificationStats_footerFlagOn_clearableSilent() {
+ fun testSetNotificationStats_clearableSilent() {
whenever(section.bucket).thenReturn(BUCKET_SILENT)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
verify(activeNotificationsInteractor)
@@ -236,12 +145,8 @@ class StackCoordinatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(
- FooterViewRefactor.FLAG_NAME,
- FLAG_SCREENSHARE_NOTIFICATION_HIDING,
- FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX,
- )
- fun testSetNotificationStats_footerFlagOn_isSensitiveStateActive_nonClearableSilent() {
+ @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING, FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX)
+ fun testSetNotificationStats_isSensitiveStateActive_nonClearableSilent() {
whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
whenever(section.bucket).thenReturn(BUCKET_SILENT)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
@@ -259,8 +164,7 @@ class StackCoordinatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
- fun testSetNotificationStats_footerFlagOn_nonClearableRedacted() {
+ fun testSetNotificationStats_nonClearableRedacted() {
entry.setSensitive(true, true)
whenever(section.bucket).thenReturn(BUCKET_ALERTING)
afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
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 25138fd0ff83..57a12df0cfee 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
@@ -38,6 +38,7 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapperTest.Companion.any
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -123,7 +124,8 @@ class IconManagerTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun testCreateIcons_chipNotifIconFlagEnabled_statusBarChipIconIsNull() {
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagDisabled_statusBarChipIconIsNotNull() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
entry?.let { iconManager.createIcons(it) }
@@ -133,6 +135,17 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun testCreateIcons_chipNotifIconFlagEnabled_cdFlagEnabled_statusBarChipIconIsNull() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
+ entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
+
+ assertThat(entry?.icons?.statusBarChipIcon).isNull()
+ }
+
+ @Test
fun testCreateIcons_importantConversation_shortcutIcon() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = true)
@@ -158,7 +171,7 @@ class IconManagerTest : SysuiTestCase() {
notificationEntry(
hasShortcut = false,
hasMessageSenderIcon = false,
- hasLargeIcon = true
+ hasLargeIcon = true,
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
@@ -172,7 +185,7 @@ class IconManagerTest : SysuiTestCase() {
notificationEntry(
hasShortcut = false,
hasMessageSenderIcon = false,
- hasLargeIcon = false
+ hasLargeIcon = false,
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
@@ -187,7 +200,7 @@ class IconManagerTest : SysuiTestCase() {
hasShortcut = true,
hasMessageSenderIcon = true,
useMessagingStyle = false,
- hasLargeIcon = true
+ hasLargeIcon = true,
)
entry?.channel?.isImportantConversation = true
entry?.let { iconManager.createIcons(it) }
@@ -205,7 +218,8 @@ class IconManagerTest : SysuiTestCase() {
@Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun testCreateIcons_sensitiveImportantConversation() {
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun testCreateIcons_cdFlagDisabled_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
entry?.setSensitive(true, true)
@@ -219,8 +233,24 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun testCreateIcons_cdFlagEnabled_sensitiveImportantConversation() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
+ entry?.setSensitive(true, true)
+ entry?.channel?.isImportantConversation = true
+ entry?.let { iconManager.createIcons(it) }
+ testScope.runCurrent()
+ assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.statusBarChipIcon).isNull()
+ assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
+ assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
+ }
+
+ @Test
@EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON)
- fun testUpdateIcons_sensitiveImportantConversation() {
+ @DisableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+ fun testUpdateIcons_cdFlagDisabled_sensitiveImportantConversation() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
entry?.setSensitive(true, true)
@@ -236,6 +266,23 @@ class IconManagerTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(FLAG_STATUS_BAR_CALL_CHIP_NOTIFICATION_ICON, StatusBarConnectedDisplays.FLAG_NAME)
+ fun testUpdateIcons_cdFlagEnabled_sensitiveImportantConversation() {
+ val entry =
+ notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
+ entry?.setSensitive(true, true)
+ entry?.channel?.isImportantConversation = true
+ entry?.let { iconManager.createIcons(it) }
+ // Updating the icons after creation shouldn't break anything
+ entry?.let { iconManager.updateIcons(it) }
+ testScope.runCurrent()
+ assertThat(entry?.icons?.statusBarIcon?.sourceIcon).isEqualTo(shortcutIc)
+ assertThat(entry?.icons?.statusBarChipIcon).isNull()
+ assertThat(entry?.icons?.shelfIcon?.sourceIcon).isEqualTo(smallIc)
+ assertThat(entry?.icons?.aodIcon?.sourceIcon).isEqualTo(smallIc)
+ }
+
+ @Test
fun testUpdateIcons_sensitivityChange() {
val entry =
notificationEntry(hasShortcut = true, hasMessageSenderIcon = true, hasLargeIcon = false)
@@ -254,7 +301,7 @@ class IconManagerTest : SysuiTestCase() {
hasShortcut: Boolean,
hasMessageSenderIcon: Boolean,
useMessagingStyle: Boolean = true,
- hasLargeIcon: Boolean
+ hasLargeIcon: Boolean,
): NotificationEntry? {
val n =
Notification.Builder(mContext, "id")
@@ -270,7 +317,7 @@ class IconManagerTest : SysuiTestCase() {
SystemClock.currentThreadTimeMillis(),
Person.Builder()
.setIcon(if (hasMessageSenderIcon) messageIc else null)
- .build()
+ .build(),
)
)
if (useMessagingStyle) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index e1a891662889..3763282cdebc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.stack;
-import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.flags.SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag;
@@ -28,17 +27,14 @@ import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
-import static org.mockito.AdditionalMatchers.not;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
@@ -64,7 +60,6 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
-import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -92,8 +87,6 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.emptyshade.shared.ModesEmptyShadeFix;
import com.android.systemui.statusbar.notification.emptyshade.ui.view.EmptyShadeView;
-import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor;
-import com.android.systemui.statusbar.notification.footer.shared.NotifRedesignFooter;
import com.android.systemui.statusbar.notification.footer.ui.view.FooterView;
import com.android.systemui.statusbar.notification.headsup.AvalancheController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -603,158 +596,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void manageNotifications_visible() {
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- when(view.willBeGone()).thenReturn(true);
-
- mStackScroller.updateFooterView(true, false, true);
-
- verify(view).setVisible(eq(true), anyBoolean());
- verify(view).setClearAllButtonVisible(eq(false), anyBoolean());
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void clearAll_visible() {
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- when(view.willBeGone()).thenReturn(true);
-
- mStackScroller.updateFooterView(true, true, true);
-
- verify(view).setVisible(eq(true), anyBoolean());
- verify(view).setClearAllButtonVisible(eq(true), anyBoolean());
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testInflateFooterView() {
- mStackScroller.inflateFooterView();
- ArgumentCaptor<FooterView> captor = ArgumentCaptor.forClass(FooterView.class);
- verify(mStackScroller).setFooterView(captor.capture());
-
- assertNotNull(captor.getValue().findViewById(R.id.manage_text));
- assertNotNull(captor.getValue().findViewById(R.id.dismiss_text));
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testUpdateFooter_noNotifications() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- @DisableSceneContainer
- public void testUpdateFooter_remoteInput() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- mStackScroller.setIsRemoteInputActive(true);
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(true);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testUpdateFooter_withoutNotifications() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(false);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(false, false, true);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- @DisableSceneContainer
- public void testUpdateFooter_oneClearableNotification() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(true);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, true);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- @DisableSceneContainer
- public void testUpdateFooter_withoutHistory() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(false);
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(true);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(true, true, false);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void testUpdateFooter_oneClearableNotification_beforeUserSetup() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(false);
-
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(true);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(false, true, true);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- @DisableSceneContainer
- public void testUpdateFooter_oneNonClearableNotification() {
- setBarStateForTest(StatusBarState.SHADE);
- mStackScroller.setCurrentUserSetup(true);
-
- when(mStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
- when(mStackScrollLayoutController.hasActiveClearableNotifications(eq(ROWS_ALL)))
- .thenReturn(false);
- when(mEmptyShadeView.getVisibility()).thenReturn(GONE);
-
- FooterView view = mock(FooterView.class);
- mStackScroller.setFooterView(view);
- mStackScroller.updateFooter();
- verify(mStackScroller, atLeastOnce()).updateFooterView(true, false, true);
- }
-
- @Test
public void testFooterPosition_atEnd() {
// add footer
FooterView view = mock(FooterView.class);
@@ -772,19 +613,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME,
- ModesEmptyShadeFix.FLAG_NAME,
- NotifRedesignFooter.FLAG_NAME})
- public void testReInflatesFooterViews() {
- when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
- clearInvocations(mStackScroller);
- mStackScroller.reinflateViews();
- verify(mStackScroller).setFooterView(any());
- verify(mStackScroller).setEmptyShadeView(any());
- }
-
- @Test
- @EnableFlags(FooterViewRefactor.FLAG_NAME)
@DisableFlags(ModesEmptyShadeFix.FLAG_NAME)
public void testReInflatesEmptyShadeView() {
when(mEmptyShadeView.getTextResource()).thenReturn(R.string.empty_shade_text);
@@ -1231,31 +1059,6 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, NotifRedesignFooter.FLAG_NAME})
- public void hasFilteredOutSeenNotifs_updateFooter() {
- mStackScroller.setCurrentUserSetup(true);
-
- // add footer
- mStackScroller.inflateFooterView();
- TextView footerLabel =
- mStackScroller.mFooterView.requireViewById(R.id.unlock_prompt_footer);
-
- mStackScroller.setHasFilteredOutSeenNotifications(true);
- mStackScroller.updateFooter();
-
- assertThat(footerLabel.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableFlags({FooterViewRefactor.FLAG_NAME, ModesEmptyShadeFix.FLAG_NAME})
- public void hasFilteredOutSeenNotifs_updateEmptyShadeView() {
- mStackScroller.setHasFilteredOutSeenNotifications(true);
- mStackScroller.updateEmptyShadeView(true, false);
-
- verify(mEmptyShadeView).setFooterText(not(eq(0)));
- }
-
- @Test
@DisableSceneContainer
public void testWindowInsetAnimationProgress_updatesBottomInset() {
int imeInset = 100;
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
index 59ad38a87146..59ad38a87146 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/RefreshUsersSchedulerTest.kt
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
index 17093291e8b0..2a1877adc172 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/animation/FakeDialogTransitionAnimator.kt
@@ -24,7 +24,6 @@ fun fakeDialogTransitionAnimator(
@Main mainExecutor: Executor,
isUnlocked: Boolean = true,
isShowingAlternateAuthOnUnlock: Boolean = false,
- isPredictiveBackQsDialogAnim: Boolean = false,
interactionJankMonitor: InteractionJankMonitor,
): DialogTransitionAnimator {
return DialogTransitionAnimator(
@@ -35,10 +34,6 @@ fun fakeDialogTransitionAnimator(
isShowingAlternateAuthOnUnlock = isShowingAlternateAuthOnUnlock,
),
interactionJankMonitor = interactionJankMonitor,
- featureFlags =
- object : AnimationFeatureFlags {
- override val isPredictiveBackQsDialogAnim = isPredictiveBackQsDialogAnim
- },
transitionAnimator = fakeTransitionAnimator(mainExecutor),
isForTesting = true,
)
@@ -50,6 +45,8 @@ private class FakeCallback(
private val isShowingAlternateAuthOnUnlock: Boolean = false,
) : DialogTransitionAnimator.Callback {
override fun isDreaming(): Boolean = isDreaming
+
override fun isUnlocked(): Boolean = isUnlocked
+
override fun isShowingAlternateAuthOnUnlock() = isShowingAlternateAuthOnUnlock
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
new file mode 100644
index 000000000000..fb6699c44d62
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/compose/Snapshot.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose
+
+import androidx.compose.runtime.snapshots.Snapshot
+import com.android.systemui.kosmos.runCurrent
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+
+/**
+ * Runs the given test [block] in a [TestScope] that's set up such that the Compose snapshot state
+ * is settled eagerly. This is the Compose equivalent to using an [UnconfinedTestDispatcher] or
+ * using [runCurrent] a lot.
+ *
+ * Note that this shouldn't be needed or used in a Compose test environment.
+ */
+fun TestScope.runTestWithSnapshots(block: suspend TestScope.() -> Unit) {
+ val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() }
+
+ try {
+ runTest { block() }
+ } finally {
+ handle.dispose()
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index 47991b3b9689..3df3ee983ecf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -154,6 +154,7 @@ val Kosmos.shortcutHelperCoreStartable by
shortcutHelperStateRepository,
activityStarter,
testScope,
+ customInputGesturesRepository
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 1288d3151051..8489d8380041 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -46,9 +46,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
MutableSharedFlow(extraBufferCapacity = 1)
override val keyguardDoneAnimationsFinished: Flow<Unit> = _keyguardDoneAnimationsFinished
- private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true)
- override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered
-
private val _dismissAction = MutableStateFlow<DismissAction>(DismissAction.None)
override val dismissAction: StateFlow<DismissAction> = _dismissAction
@@ -192,10 +189,6 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
_keyguardDoneAnimationsFinished.tryEmit(Unit)
}
- override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
- _clockShouldBeCentered.value = shouldBeCentered
- }
-
override fun setKeyguardEnabled(enabled: Boolean) {
_isKeyguardEnabled.value = enabled
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
index 8209ee12ad9a..f4791003c828 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -402,6 +402,12 @@ class FakeKeyguardTransitionRepository(
)
)
}
+
+ suspend fun transitionTo(from: KeyguardState, to: KeyguardState) {
+ sendTransitionStep(TransitionStep(from, to, 0f, TransitionState.STARTED))
+ sendTransitionStep(TransitionStep(from, to, 0.5f, TransitionState.RUNNING))
+ sendTransitionStep(TransitionStep(from, to, 1f, TransitionState.FINISHED))
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
index 1881a94c8984..afe48214832f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt
@@ -1,7 +1,7 @@
package com.android.systemui.kosmos
-import androidx.compose.runtime.snapshots.Snapshot
import com.android.systemui.SysuiTestCase
+import com.android.systemui.compose.runTestWithSnapshots
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
@@ -17,7 +17,6 @@ import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
import org.mockito.kotlin.verify
var Kosmos.testDispatcher by Fixture { StandardTestDispatcher() }
@@ -53,26 +52,10 @@ var Kosmos.brightnessWarningToast: BrightnessWarningToast by
/**
* Run this test body with a [Kosmos] as receiver, and using the [testScope] currently installed in
- * that kosmos instance
+ * that Kosmos instance
*/
-fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) =
- testScope.runTest testBody@{ this@runTest.testBody() }
-
-/**
- * Runs the given [Kosmos]-scoped test [block] in an environment where compose snapshot state is
- * settled eagerly. This is the compose equivalent to using an [UnconfinedTestDispatcher] or using
- * [runCurrent] a lot.
- *
- * Note that this shouldn't be needed or used in a compose test environment.
- */
-fun Kosmos.runTestWithSnapshots(block: suspend Kosmos.() -> Unit) {
- val handle = Snapshot.registerGlobalWriteObserver { Snapshot.sendApplyNotifications() }
-
- try {
- testScope.runTest { block() }
- } finally {
- handle.dispose()
- }
+fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) {
+ testScope.runTestWithSnapshots testBody@{ this@runTest.testBody() }
}
fun Kosmos.runCurrent() = testScope.runCurrent()
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt
new file mode 100644
index 000000000000..bcba5ee50b8c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/ui/view/SceneJankMonitorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.scene.ui.view
+
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.sceneJankMonitorFactory: SceneJankMonitor.Factory by Fixture {
+ object : SceneJankMonitor.Factory {
+ override fun create(): SceneJankMonitor {
+ return SceneJankMonitor(
+ authenticationInteractor = authenticationInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
+ interactionJankMonitor = interactionJankMonitor,
+ )
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
index 44917dd4ba48..198d72a41fa4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.dialog.sliders.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.plugins.volumeDialogController
+import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
import com.android.systemui.volume.dialog.domain.interactor.volumeDialogStateInteractor
import com.android.systemui.volume.dialog.sliders.domain.model.volumeDialogSliderType
@@ -29,5 +30,6 @@ val Kosmos.volumeDialogSliderInteractor: VolumeDialogSliderInteractor by
applicationCoroutineScope,
volumeDialogStateInteractor,
volumeDialogController,
+ zenModeInteractor,
)
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 648990588d29..3a38152825c9 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -420,5 +420,9 @@ message SystemMessage {
// Notify the user that accessibility floating menu is hidden.
// Package: com.android.systemui
NOTE_A11Y_FLOATING_MENU_HIDDEN = 1009;
+
+ // Notify the hearing aid user that input device can be changed to builtin device or hearing device.
+ // Package: android
+ NOTE_HEARING_DEVICE_INPUT_SWITCH = 1012;
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 37d045bf6422..6cd1f721d215 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -413,6 +413,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>();
private final FlashNotificationsController mFlashNotificationsController;
+ private final HearingDevicePhoneCallNotificationController mHearingDeviceNotificationController;
private final UserManagerInternal mUmi;
private AccessibilityUserState getCurrentUserStateLocked() {
@@ -569,6 +570,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// TODO(b/255426725): not used on tests
mVisibleBgUserIds = null;
mInputManager = context.getSystemService(InputManager.class);
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(
+ context);
+ } else {
+ mHearingDeviceNotificationController = null;
+ }
init();
}
@@ -618,6 +625,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
} else {
mVisibleBgUserIds = null;
}
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ mHearingDeviceNotificationController = new HearingDevicePhoneCallNotificationController(
+ context);
+ } else {
+ mHearingDeviceNotificationController = null;
+ }
init();
}
@@ -630,6 +643,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
if (enableTalkbackAndMagnifierKeyGestures()) {
mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
}
+ if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
+ if (mHearingDeviceNotificationController != null) {
+ mHearingDeviceNotificationController.startListenForCallState();
+ }
+ }
disableAccessibilityMenuToMigrateIfNeeded();
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
index e3d7062ddb4e..b94fa2f59162 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickController.java
@@ -22,6 +22,7 @@ import static com.android.server.accessibility.AutoclickIndicatorView.SHOW_INDIC
import android.accessibilityservice.AccessibilityTrace;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
@@ -69,7 +70,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
// Lazily created on the first mouse motion event.
private ClickScheduler mClickScheduler;
- private ClickDelayObserver mClickDelayObserver;
+ private AutoclickSettingsObserver mAutoclickSettingsObserver;
private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
private AutoclickIndicatorView mAutoclickIndicatorView;
private WindowManager mWindowManager;
@@ -89,14 +90,17 @@ public class AutoclickController extends BaseEventStreamTransformation {
if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
if (mClickScheduler == null) {
Handler handler = new Handler(mContext.getMainLooper());
- mClickScheduler =
- new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
- mClickDelayObserver = new ClickDelayObserver(mUserId, handler);
- mClickDelayObserver.start(mContext.getContentResolver(), mClickScheduler);
-
if (Flags.enableAutoclickIndicator()) {
initiateAutoclickIndicator(handler);
}
+
+ mClickScheduler =
+ new ClickScheduler(handler, AccessibilityManager.AUTOCLICK_DELAY_DEFAULT);
+ mAutoclickSettingsObserver = new AutoclickSettingsObserver(mUserId, handler);
+ mAutoclickSettingsObserver.start(
+ mContext.getContentResolver(),
+ mClickScheduler,
+ mAutoclickIndicatorScheduler);
}
handleMouseMotion(event, policyFlags);
@@ -156,9 +160,9 @@ public class AutoclickController extends BaseEventStreamTransformation {
@Override
public void onDestroy() {
- if (mClickDelayObserver != null) {
- mClickDelayObserver.stop();
- mClickDelayObserver = null;
+ if (mAutoclickSettingsObserver != null) {
+ mAutoclickSettingsObserver.stop();
+ mAutoclickSettingsObserver = null;
}
if (mClickScheduler != null) {
mClickScheduler.cancel();
@@ -191,19 +195,24 @@ public class AutoclickController extends BaseEventStreamTransformation {
}
/**
- * Observes setting value for autoclick delay, and updates ClickScheduler delay whenever the
- * setting value changes.
+ * Observes autoclick setting values, and updates ClickScheduler delay and indicator size
+ * whenever the setting value changes.
*/
- final private static class ClickDelayObserver extends ContentObserver {
+ final private static class AutoclickSettingsObserver extends ContentObserver {
/** URI used to identify the autoclick delay setting with content resolver. */
private final Uri mAutoclickDelaySettingUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_DELAY);
+ /** URI used to identify the autoclick cursor area size setting with content resolver. */
+ private final Uri mAutoclickCursorAreaSizeSettingUri =
+ Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE);
+
private ContentResolver mContentResolver;
private ClickScheduler mClickScheduler;
+ private AutoclickIndicatorScheduler mAutoclickIndicatorScheduler;
private final int mUserId;
- public ClickDelayObserver(int userId, Handler handler) {
+ public AutoclickSettingsObserver(int userId, Handler handler) {
super(handler);
mUserId = userId;
}
@@ -216,11 +225,13 @@ public class AutoclickController extends BaseEventStreamTransformation {
* changes.
* @param clickScheduler ClickScheduler that should be updated when click delay changes.
* @throws IllegalStateException If internal state is already setup when the method is
- * called.
+ * called.
* @throws NullPointerException If any of the arguments is a null pointer.
*/
- public void start(@NonNull ContentResolver contentResolver,
- @NonNull ClickScheduler clickScheduler) {
+ public void start(
+ @NonNull ContentResolver contentResolver,
+ @NonNull ClickScheduler clickScheduler,
+ @Nullable AutoclickIndicatorScheduler autoclickIndicatorScheduler) {
if (mContentResolver != null || mClickScheduler != null) {
throw new IllegalStateException("Observer already started.");
}
@@ -233,11 +244,20 @@ public class AutoclickController extends BaseEventStreamTransformation {
mContentResolver = contentResolver;
mClickScheduler = clickScheduler;
+ mAutoclickIndicatorScheduler = autoclickIndicatorScheduler;
mContentResolver.registerContentObserver(mAutoclickDelaySettingUri, false, this,
mUserId);
// Initialize mClickScheduler's initial delay value.
onChange(true, mAutoclickDelaySettingUri);
+
+ if (Flags.enableAutoclickIndicator()) {
+ // Register observer to listen to cursor area size setting change.
+ mContentResolver.registerContentObserver(
+ mAutoclickCursorAreaSizeSettingUri, false, this, mUserId);
+ // Initialize mAutoclickIndicatorView's initial size.
+ onChange(true, mAutoclickCursorAreaSizeSettingUri);
+ }
}
/**
@@ -248,7 +268,7 @@ public class AutoclickController extends BaseEventStreamTransformation {
*/
public void stop() {
if (mContentResolver == null || mClickScheduler == null) {
- throw new IllegalStateException("ClickDelayObserver not started.");
+ throw new IllegalStateException("AutoclickSettingsObserver not started.");
}
mContentResolver.unregisterContentObserver(this);
@@ -262,6 +282,18 @@ public class AutoclickController extends BaseEventStreamTransformation {
AccessibilityManager.AUTOCLICK_DELAY_DEFAULT, mUserId);
mClickScheduler.updateDelay(delay);
}
+ if (Flags.enableAutoclickIndicator()
+ && mAutoclickCursorAreaSizeSettingUri.equals(uri)) {
+ int size =
+ Settings.Secure.getIntForUser(
+ mContentResolver,
+ Settings.Secure.ACCESSIBILITY_AUTOCLICK_CURSOR_AREA_SIZE,
+ AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT,
+ mUserId);
+ if (mAutoclickIndicatorScheduler != null) {
+ mAutoclickIndicatorScheduler.updateCursorAreaSize(size);
+ }
+ }
}
}
@@ -317,6 +349,10 @@ public class AutoclickController extends BaseEventStreamTransformation {
mScheduledShowIndicatorTime = -1;
mHandler.removeCallbacks(this);
}
+
+ public void updateCursorAreaSize(int size) {
+ mAutoclickIndicatorView.setRadius(size);
+ }
}
/**
diff --git a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
index 816d8e456a9a..bf5015176f8c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
+++ b/services/accessibility/java/com/android/server/accessibility/AutoclickIndicatorView.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.view.accessibility.AccessibilityManager.AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -35,8 +37,7 @@ public class AutoclickIndicatorView extends View {
static final int MINIMAL_ANIMATION_DURATION = 50;
- // TODO(b/383901288): allow users to customize the indicator area.
- static final float RADIUS = 50;
+ private float mRadius = AUTOCLICK_CURSOR_AREA_SIZE_DEFAULT;
private final Paint mPaint;
@@ -84,10 +85,10 @@ public class AutoclickIndicatorView extends View {
if (showIndicator) {
mRingRect.set(
- /* left= */ mX - RADIUS,
- /* top= */ mY - RADIUS,
- /* right= */ mX + RADIUS,
- /* bottom= */ mY + RADIUS);
+ /* left= */ mX - mRadius,
+ /* top= */ mY - mRadius,
+ /* right= */ mX + mRadius,
+ /* bottom= */ mY + mRadius);
canvas.drawArc(mRingRect, /* startAngle= */ -90, mSweepAngle, false, mPaint);
}
}
@@ -107,6 +108,10 @@ public class AutoclickIndicatorView extends View {
mY = y;
}
+ public void setRadius(int radius) {
+ mRadius = radius;
+ }
+
public void redrawIndicator() {
showIndicator = true;
invalidate();
diff --git a/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
new file mode 100644
index 000000000000..d06daf5db127
--- /dev/null
+++ b/services/accessibility/java/com/android/server/accessibility/HearingDevicePhoneCallNotificationController.java
@@ -0,0 +1,356 @@
+/*
+ * 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.accessibility;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+/**
+ * A controller class to handle notification for hearing device during phone calls.
+ */
+public class HearingDevicePhoneCallNotificationController {
+
+ private final TelephonyManager mTelephonyManager;
+ private final TelephonyCallback mTelephonyListener;
+ private final Executor mCallbackExecutor;
+
+ public HearingDevicePhoneCallNotificationController(@NonNull Context context) {
+ mTelephonyListener = new CallStateListener(context);
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallbackExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ @VisibleForTesting
+ HearingDevicePhoneCallNotificationController(@NonNull Context context,
+ TelephonyCallback telephonyCallback) {
+ mTelephonyListener = telephonyCallback;
+ mTelephonyManager = context.getSystemService(TelephonyManager.class);
+ mCallbackExecutor = context.getMainExecutor();
+ }
+
+ /**
+ * Registers a telephony callback to listen for call state changed to handle notification for
+ * hearing device during phone calls.
+ */
+ public void startListenForCallState() {
+ mTelephonyManager.registerTelephonyCallback(mCallbackExecutor, mTelephonyListener);
+ }
+
+ /**
+ * A telephony callback listener to listen to call state changes and show/dismiss notification
+ */
+ @VisibleForTesting
+ static class CallStateListener extends TelephonyCallback implements
+ TelephonyCallback.CallStateListener {
+
+ private static final String TAG =
+ "HearingDevice_CallStateListener";
+ private static final String ACTION_SWITCH_TO_BUILTIN_MIC =
+ "com.android.server.accessibility.hearingdevice.action.SWITCH_TO_BUILTIN_MIC";
+ private static final String ACTION_SWITCH_TO_HEARING_MIC =
+ "com.android.server.accessibility.hearingdevice.action.SWITCH_TO_HEARING_MIC";
+ private static final String ACTION_BLUETOOTH_DEVICE_DETAILS =
+ "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS";
+ private static final String KEY_BLUETOOTH_ADDRESS = "device_address";
+ private static final String EXTRA_SHOW_FRAGMENT_ARGUMENTS = ":settings:show_fragment_args";
+ private static final int MICROPHONE_SOURCE_VOICE_COMMUNICATION =
+ MediaRecorder.AudioSource.VOICE_COMMUNICATION;
+ private static final AudioDeviceAttributes BUILTIN_MIC = new AudioDeviceAttributes(
+ AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, "");
+
+ private final Context mContext;
+ private NotificationManager mNotificationManager;
+ private AudioManager mAudioManager;
+ private BroadcastReceiver mHearingDeviceActionReceiver;
+ private BluetoothDevice mHearingDevice;
+ private boolean mIsNotificationShown = false;
+
+ CallStateListener(@NonNull Context context) {
+ mContext = context;
+ }
+
+ @Override
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ public void onCallStateChanged(int state) {
+ // NotificationManagerService and AudioService are all initialized after
+ // AccessibilityManagerService.
+ // Can not get them in constructor. Need to get these services until callback is
+ // triggered.
+ mNotificationManager = mContext.getSystemService(NotificationManager.class);
+ mAudioManager = mContext.getSystemService(AudioManager.class);
+ if (mNotificationManager == null || mAudioManager == null) {
+ Log.w(TAG, "NotificationManager or AudioManager is not prepare yet.");
+ return;
+ }
+
+ if (state == TelephonyManager.CALL_STATE_IDLE) {
+ dismissNotificationIfNeeded();
+
+ if (mHearingDevice != null) {
+ // reset to its original status
+ setMicrophonePreferredForCalls(mHearingDevice.isMicrophonePreferredForCalls());
+ }
+ mHearingDevice = null;
+ }
+ if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+ mHearingDevice = getSupportedInputHearingDeviceInfo(
+ mAudioManager.getAvailableCommunicationDevices());
+ if (mHearingDevice != null) {
+ showNotificationIfNeeded();
+ }
+ }
+ }
+
+ private void showNotificationIfNeeded() {
+ if (mIsNotificationShown) {
+ return;
+ }
+
+ showNotification(mHearingDevice.isMicrophonePreferredForCalls());
+ mIsNotificationShown = true;
+ }
+
+ private void dismissNotificationIfNeeded() {
+ if (!mIsNotificationShown) {
+ return;
+ }
+
+ dismissNotification();
+ mIsNotificationShown = false;
+ }
+
+ private void showNotification(boolean useRemoteMicrophone) {
+ mNotificationManager.notify(
+ SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH,
+ createSwitchInputNotification(useRemoteMicrophone));
+ registerReceiverIfNeeded();
+ }
+
+ private void dismissNotification() {
+ unregisterReceiverIfNeeded();
+ mNotificationManager.cancel(
+ SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH);
+ }
+
+ private BluetoothDevice getSupportedInputHearingDeviceInfo(List<AudioDeviceInfo> infoList) {
+ final BluetoothAdapter bluetoothAdapter = mContext.getSystemService(
+ BluetoothManager.class).getAdapter();
+ if (bluetoothAdapter == null) {
+ return null;
+ }
+ if (!isHapClientSupported()) {
+ return null;
+ }
+
+ final Set<String> inputDeviceAddress = Arrays.stream(
+ mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).map(
+ AudioDeviceInfo::getAddress).collect(Collectors.toSet());
+
+ //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
+ final AudioDeviceInfo hearingDeviceInfo = infoList.stream()
+ .filter(info -> info.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET)
+ .filter(info -> inputDeviceAddress.contains(info.getAddress()))
+ .filter(info -> isHapClientDevice(bluetoothAdapter, info))
+ .findAny()
+ .orElse(null);
+
+ return (hearingDeviceInfo != null) ? bluetoothAdapter.getRemoteDevice(
+ hearingDeviceInfo.getAddress()) : null;
+ }
+
+ @VisibleForTesting
+ boolean isHapClientDevice(BluetoothAdapter bluetoothAdapter, AudioDeviceInfo info) {
+ BluetoothDevice device = bluetoothAdapter.getRemoteDevice(info.getAddress());
+ return ArrayUtils.contains(device.getUuids(), BluetoothUuid.HAS);
+ }
+
+ @VisibleForTesting
+ boolean isHapClientSupported() {
+ return BluetoothAdapter.getDefaultAdapter().getSupportedProfiles().contains(
+ BluetoothProfile.HAP_CLIENT);
+ }
+
+ private Notification createSwitchInputNotification(boolean useRemoteMicrophone) {
+ return new Notification.Builder(mContext,
+ SystemNotificationChannels.ACCESSIBILITY_HEARING_DEVICE)
+ .setContentTitle(getSwitchInputTitle(useRemoteMicrophone))
+ .setContentText(getSwitchInputMessage(useRemoteMicrophone))
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setContentIntent(createPendingIntent(ACTION_BLUETOOTH_DEVICE_DETAILS))
+ .setActions(buildSwitchInputAction(useRemoteMicrophone),
+ buildOpenSettingsAction())
+ .build();
+ }
+
+ private Notification.Action buildSwitchInputAction(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_switch_button),
+ createPendingIntent(ACTION_SWITCH_TO_BUILTIN_MIC)).build()
+ : new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_switch_button),
+ createPendingIntent(ACTION_SWITCH_TO_HEARING_MIC)).build();
+ }
+
+ private Notification.Action buildOpenSettingsAction() {
+ return new Notification.Action.Builder(null,
+ mContext.getString(R.string.hearing_device_notification_settings_button),
+ createPendingIntent(ACTION_BLUETOOTH_DEVICE_DETAILS)).build();
+ }
+
+ private PendingIntent createPendingIntent(String action) {
+ final Intent intent = new Intent(action);
+
+ switch (action) {
+ case ACTION_SWITCH_TO_BUILTIN_MIC, ACTION_SWITCH_TO_HEARING_MIC -> {
+ intent.setPackage(mContext.getPackageName());
+ return PendingIntent.getBroadcast(mContext, /* requestCode = */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+ case ACTION_BLUETOOTH_DEVICE_DETAILS -> {
+ Bundle bundle = new Bundle();
+ bundle.putString(KEY_BLUETOOTH_ADDRESS, mHearingDevice.getAddress());
+ intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, bundle);
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ return PendingIntent.getActivity(mContext, /* requestCode = */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+ }
+ }
+ return null;
+ }
+
+ private void setMicrophonePreferredForCalls(boolean useRemoteMicrophone) {
+ if (useRemoteMicrophone) {
+ switchToHearingMic();
+ } else {
+ switchToBuiltinMic();
+ }
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void switchToBuiltinMic() {
+ mAudioManager.clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ mAudioManager.setPreferredDeviceForCapturePreset(MICROPHONE_SOURCE_VOICE_COMMUNICATION,
+ BUILTIN_MIC);
+ }
+
+ @SuppressLint("AndroidFrameworkRequiresPermission")
+ private void switchToHearingMic() {
+ // clear config to let audio manager to determine next priority device. We can assume
+ // user connects to hearing device here, so next priority device should be hearing
+ // device.
+ mAudioManager.clearPreferredDevicesForCapturePreset(
+ MICROPHONE_SOURCE_VOICE_COMMUNICATION);
+ }
+
+ private void registerReceiverIfNeeded() {
+ if (mHearingDeviceActionReceiver != null) {
+ return;
+ }
+ mHearingDeviceActionReceiver = new HearingDeviceActionReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_SWITCH_TO_BUILTIN_MIC);
+ intentFilter.addAction(ACTION_SWITCH_TO_HEARING_MIC);
+ mContext.registerReceiver(mHearingDeviceActionReceiver, intentFilter,
+ Manifest.permission.MANAGE_ACCESSIBILITY, null, Context.RECEIVER_NOT_EXPORTED);
+ }
+
+ private void unregisterReceiverIfNeeded() {
+ if (mHearingDeviceActionReceiver == null) {
+ return;
+ }
+ mContext.unregisterReceiver(mHearingDeviceActionReceiver);
+ mHearingDeviceActionReceiver = null;
+ }
+
+ private CharSequence getSwitchInputTitle(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? mContext.getString(
+ R.string.hearing_device_switch_phone_mic_notification_title)
+ : mContext.getString(
+ R.string.hearing_device_switch_hearing_mic_notification_title);
+ }
+
+ private CharSequence getSwitchInputMessage(boolean useRemoteMicrophone) {
+ return useRemoteMicrophone
+ ? mContext.getString(
+ R.string.hearing_device_switch_phone_mic_notification_text)
+ : mContext.getString(
+ R.string.hearing_device_switch_hearing_mic_notification_text);
+ }
+
+ private class HearingDeviceActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (TextUtils.isEmpty(action)) {
+ return;
+ }
+
+ if (ACTION_SWITCH_TO_BUILTIN_MIC.equals(action)) {
+ switchToBuiltinMic();
+ showNotification(/* useRemoteMicrophone= */ false);
+ } else if (ACTION_SWITCH_TO_HEARING_MIC.equals(action)) {
+ switchToHearingMic();
+ showNotification(/* useRemoteMicrophone= */ true);
+ }
+ }
+ }
+ }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
index f15b8eec3f6b..cd46b38272c2 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchState.java
@@ -38,7 +38,7 @@ public class TouchState {
// Pointer-related constants
// This constant captures the current implementation detail that
// pointer IDs are between 0 and 31 inclusive (subject to change).
- // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
+ // (See MAX_POINTER_ID in frameworks/native/include/input/Input.h)
public static final int MAX_POINTER_COUNT = 32;
// Constant referring to the ids bits of all pointers.
public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 418f3a18688b..0e2e50589217 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -109,6 +109,8 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService {
@@ -226,7 +228,8 @@ public class CompanionDeviceManagerService extends SystemService {
if (associations.isEmpty()) return;
mCompanionExemptionProcessor.updateAtm(userId, associations);
- mCompanionExemptionProcessor.updateAutoRevokeExemptions();
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ executor.execute(mCompanionExemptionProcessor::updateAutoRevokeExemptions);
}
@Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index d3e808fbd3d1..7456c5099698 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -265,8 +265,8 @@ class InputController {
mInputManagerInternal.setPointerIconVisible(visible, displayId);
}
- void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
- mInputManagerInternal.setMousePointerAccelerationEnabled(enabled, displayId);
+ void setMouseScalingEnabled(boolean enabled, int displayId) {
+ mInputManagerInternal.setMouseScalingEnabled(enabled, displayId);
}
void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 6bf60bf1ddf1..260ea75a1f4c 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1518,7 +1518,7 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
final long token = Binder.clearCallingIdentity();
try {
- mInputController.setMousePointerAccelerationEnabled(false, displayId);
+ mInputController.setMouseScalingEnabled(false, displayId);
mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false,
displayId);
if (isTrustedDisplay) {
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 1f3b31692289..aeb2f5e9be84 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -176,7 +176,7 @@ public class VirtualDeviceManagerService extends SystemService {
public VirtualDeviceManagerService(Context context) {
super(context);
mImpl = new VirtualDeviceManagerImpl();
- mNativeImpl = Flags.enableNativeVdm() ? new VirtualDeviceManagerNativeImpl() : null;
+ mNativeImpl = new VirtualDeviceManagerNativeImpl();
mLocalService = new LocalService();
}
@@ -208,9 +208,7 @@ public class VirtualDeviceManagerService extends SystemService {
@RequiresPermission(android.Manifest.permission.MANAGE_COMPANION_DEVICES)
public void onStart() {
publishBinderService(Context.VIRTUAL_DEVICE_SERVICE, mImpl);
- if (Flags.enableNativeVdm()) {
- publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl);
- }
+ publishBinderService(VIRTUAL_DEVICE_NATIVE_SERVICE, mNativeImpl);
publishLocalService(VirtualDeviceManagerInternal.class, mLocalService);
ActivityTaskManagerInternal activityTaskManagerInternal = getLocalService(
ActivityTaskManagerInternal.class);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index dc830642dcc5..d6bffcb7d21d 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@ java_library_static {
srcs: [
":android.hardware.tv.hdmi.connection-V1-java-source",
":android.hardware.tv.hdmi.earc-V1-java-source",
+ ":android.hardware.tv.mediaquality-V1-java-source",
":statslog-art-java-gen",
":statslog-contexthub-java-gen",
":services.core-aidl-sources",
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 778c6864282d..31f6ef9fc062 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -1708,7 +1708,7 @@ public class BinaryTransparencyService extends SystemService {
private class PackageUpdatedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- if (!intent.getAction().equals(Intent.ACTION_PACKAGE_ADDED)) {
+ if (!Intent.ACTION_PACKAGE_ADDED.equals(intent.getAction())) {
return;
}
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index dce9760b3971..6459016eec75 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -66,8 +66,7 @@ import com.android.server.wm.WindowManagerInternal;
/**
* The service that listens for gestures detected in sensor firmware and starts the intent
* accordingly.
- * <p>For now, only camera launch gesture is supported, and in the future, more gestures can be
- * added.</p>
+ *
* @hide
*/
public class GestureLauncherService extends SystemService {
@@ -109,10 +108,22 @@ public class GestureLauncherService extends SystemService {
@VisibleForTesting
static final int EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS_MAX = 5000;
- /** Indicates camera should be launched on power double tap. */
+ /** Configuration value indicating double tap power gesture is disabled. */
+ @VisibleForTesting static final int DOUBLE_TAP_POWER_DISABLED_MODE = 0;
+
+ /** Configuration value indicating double tap power gesture should launch camera. */
+ @VisibleForTesting static final int DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE = 1;
+
+ /**
+ * Configuration value indicating double tap power gesture should launch one of many target
+ * actions.
+ */
+ @VisibleForTesting static final int DOUBLE_TAP_POWER_MULTI_TARGET_MODE = 2;
+
+ /** Indicates camera launch is selected as target action for multi target double tap power. */
@VisibleForTesting static final int LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER = 0;
- /** Indicates wallet should be launched on power double tap. */
+ /** Indicates wallet launch is selected as target action for multi target double tap power. */
@VisibleForTesting static final int LAUNCH_WALLET_ON_DOUBLE_TAP_POWER = 1;
/** Number of taps required to launch the double tap shortcut (either camera or wallet). */
@@ -228,6 +239,7 @@ public class GestureLauncherService extends SystemService {
return mId;
}
}
+
public GestureLauncherService(Context context) {
this(context, new MetricsLogger(),
QuickAccessWalletClient.create(context), new UiEventLoggerImpl());
@@ -289,16 +301,15 @@ public class GestureLauncherService extends SystemService {
Settings.Secure.getUriFor(
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE),
false, mSettingObserver, mUserId);
- } else {
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
- false, mSettingObserver, mUserId);
- mContext.getContentResolver().registerContentObserver(
- Settings.Secure.getUriFor(
- Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
- false, mSettingObserver, mUserId);
}
mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.CAMERA_GESTURE_DISABLED),
+ false, mSettingObserver, mUserId);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Secure.getUriFor(
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED),
+ false, mSettingObserver, mUserId);
+ mContext.getContentResolver().registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.CAMERA_LIFT_TRIGGER_ENABLED),
false, mSettingObserver, mUserId);
mContext.getContentResolver().registerContentObserver(
@@ -468,23 +479,27 @@ public class GestureLauncherService extends SystemService {
Settings.Secure.CAMERA_GESTURE_DISABLED, 0, userId) == 0);
}
-
/** Checks if camera should be launched on double press of the power button. */
public static boolean isCameraDoubleTapPowerSettingEnabled(Context context, int userId) {
- boolean res;
-
- if (launchWalletOptionOnPowerDoubleTap()) {
- res = isDoubleTapPowerGestureSettingEnabled(context, userId)
- && getDoubleTapPowerGestureAction(context, userId)
- == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
- } else {
- // These are legacy settings that will be deprecated once the option to launch both
- // wallet and camera has been created.
- res = isCameraDoubleTapPowerEnabled(context.getResources())
+ if (!launchWalletOptionOnPowerDoubleTap()) {
+ return isCameraDoubleTapPowerEnabled(context.getResources())
&& (Settings.Secure.getIntForUser(context.getContentResolver(),
Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0);
}
- return res;
+
+ final int doubleTapPowerGestureSettingMode = getDoubleTapPowerGestureMode(
+ context.getResources());
+
+ return switch (doubleTapPowerGestureSettingMode) {
+ case DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE -> Settings.Secure.getIntForUser(
+ context.getContentResolver(),
+ Settings.Secure.CAMERA_DOUBLE_TAP_POWER_GESTURE_DISABLED, 0, userId) == 0;
+ case DOUBLE_TAP_POWER_MULTI_TARGET_MODE ->
+ isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
+ && getDoubleTapPowerGestureAction(context, userId)
+ == LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
+ default -> false;
+ };
}
/** Checks if wallet should be launched on double tap of the power button. */
@@ -493,7 +508,9 @@ public class GestureLauncherService extends SystemService {
return false;
}
- return isDoubleTapPowerGestureSettingEnabled(context, userId)
+ return getDoubleTapPowerGestureMode(context.getResources())
+ == DOUBLE_TAP_POWER_MULTI_TARGET_MODE
+ && isMultiTargetDoubleTapPowerGestureSettingEnabled(context, userId)
&& getDoubleTapPowerGestureAction(context, userId)
== LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
}
@@ -515,26 +532,40 @@ public class GestureLauncherService extends SystemService {
isDefaultEmergencyGestureEnabled(context.getResources()) ? 1 : 0, userId) != 0;
}
- private static int getDoubleTapPowerGestureAction(Context context, int userId) {
- return Settings.Secure.getIntForUser(
- context.getContentResolver(),
- Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
- LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
- userId);
+ /** Gets the double tap power gesture mode. */
+ private static int getDoubleTapPowerGestureMode(Resources resources) {
+ return resources.getInteger(R.integer.config_doubleTapPowerGestureMode);
}
- /** Whether the shortcut to launch app on power double press is enabled. */
- private static boolean isDoubleTapPowerGestureSettingEnabled(Context context, int userId) {
+ /**
+ * Whether the setting for multi target double tap power gesture is enabled.
+ *
+ * <p>Multi target double tap power gesture allows the user to choose one of many target actions
+ * when double tapping the power button.
+ * </p>
+ */
+ private static boolean isMultiTargetDoubleTapPowerGestureSettingEnabled(Context context,
+ int userId) {
return Settings.Secure.getIntForUser(
context.getContentResolver(),
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
- isDoubleTapConfigEnabled(context.getResources()) ? 1 : 0,
+ getDoubleTapPowerGestureMode(context.getResources())
+ == DOUBLE_TAP_POWER_MULTI_TARGET_MODE ? 1 : 0,
userId)
== 1;
}
- private static boolean isDoubleTapConfigEnabled(Resources resources) {
- return resources.getBoolean(R.bool.config_doubleTapPowerGestureEnabled);
+ /** Gets the selected target action for the multi target double tap power gesture.
+ *
+ * <p>The target actions are defined in {@link Settings.Secure#DOUBLE_TAP_POWER_BUTTON_GESTURE}.
+ * </p>
+ */
+ private static int getDoubleTapPowerGestureAction(Context context, int userId) {
+ return Settings.Secure.getIntForUser(
+ context.getContentResolver(),
+ Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE,
+ LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER,
+ userId);
}
/**
@@ -595,7 +626,7 @@ public class GestureLauncherService extends SystemService {
|| isCameraLiftTriggerEnabled(resources)
|| isEmergencyGestureEnabled(resources);
if (launchWalletOptionOnPowerDoubleTap()) {
- res |= isDoubleTapConfigEnabled(resources);
+ res |= getDoubleTapPowerGestureMode(resources) != DOUBLE_TAP_POWER_DISABLED_MODE;
} else {
res |= isCameraDoubleTapPowerEnabled(resources);
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 3817ba1a28b9..0b7890167c08 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -305,6 +305,10 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
this.stringName = null;
}
+ @VisibleForTesting TempAllowListDuration getAllowlistDurationLocked(IBinder allowlistToken) {
+ return mAllowlistDuration.get(allowlistToken);
+ }
+
void setAllowBgActivityStarts(IBinder token, int flags) {
if (token == null) return;
if ((flags & FLAG_ACTIVITY_SENDER) != 0) {
@@ -323,6 +327,12 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
mAllowBgActivityStartsForActivitySender.remove(token);
mAllowBgActivityStartsForBroadcastSender.remove(token);
mAllowBgActivityStartsForServiceSender.remove(token);
+ if (mAllowlistDuration != null) {
+ mAllowlistDuration.remove(token);
+ if (mAllowlistDuration.isEmpty()) {
+ mAllowlistDuration = null;
+ }
+ }
}
public void registerCancelListenerLocked(IResultReceiver receiver) {
@@ -703,7 +713,7 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
return res;
}
- private BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
+ @VisibleForTesting BackgroundStartPrivileges getBackgroundStartPrivilegesForActivitySender(
IBinder allowlistToken) {
return mAllowBgActivityStartsForActivitySender.contains(allowlistToken)
? BackgroundStartPrivileges.allowBackgroundActivityStarts(allowlistToken)
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 295e0443371d..8a63f9a24ea3 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -359,7 +359,7 @@ public class AppOpsService extends IAppOpsService.Stub {
private static final Duration RATE_LIMITER_WINDOW = Duration.ofMillis(10);
private final RateLimiter mRateLimiter = new RateLimiter(RATE_LIMITER_WINDOW);
- volatile @NonNull HistoricalRegistry mHistoricalRegistry = new HistoricalRegistry(this);
+ volatile @NonNull HistoricalRegistry mHistoricalRegistry;
/*
* These are app op restrictions imposed per user from various parties.
@@ -1039,6 +1039,8 @@ public class AppOpsService extends IAppOpsService.Stub {
// will not exist and the nonce will be UNSET.
AppOpsManager.invalidateAppOpModeCache();
AppOpsManager.disableAppOpModeCache();
+
+ mHistoricalRegistry = new HistoricalRegistry(this, context);
}
public void publish() {
diff --git a/services/core/java/com/android/server/appop/AttributedOp.java b/services/core/java/com/android/server/appop/AttributedOp.java
index 4d114b4ad4ac..9dd09cef88f9 100644
--- a/services/core/java/com/android/server/appop/AttributedOp.java
+++ b/services/core/java/com/android/server/appop/AttributedOp.java
@@ -113,7 +113,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, accessTime,
AppOpsManager.ATTRIBUTION_FLAGS_NONE, AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE,
- DiscreteRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
+ DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP, accessCount);
}
/**
@@ -257,7 +257,8 @@ final class AttributedOp {
if (isStarted) {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, uidState, flags, startTime,
- attributionFlags, attributionChainId, DiscreteRegistry.ACCESS_TYPE_START_OP, 1);
+ attributionFlags, attributionChainId,
+ DiscreteOpsRegistry.ACCESS_TYPE_START_OP, 1);
}
}
@@ -344,8 +345,8 @@ final class AttributedOp {
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), finishedEvent.getNoteTime(), finishedEvent.getDuration(),
event.getAttributionFlags(), event.getAttributionChainId(),
- isPausing ? DiscreteRegistry.ACCESS_TYPE_PAUSE_OP
- : DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
+ isPausing ? DiscreteOpsRegistry.ACCESS_TYPE_PAUSE_OP
+ : DiscreteOpsRegistry.ACCESS_TYPE_FINISH_OP);
if (!isPausing) {
mAppOpsService.mInProgressStartOpEventPool.release(event);
@@ -453,7 +454,7 @@ final class AttributedOp {
mAppOpsService.mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid,
parent.packageName, persistentDeviceId, tag, event.getUidState(),
event.getFlags(), startTime, event.getAttributionFlags(),
- event.getAttributionChainId(), DiscreteRegistry.ACCESS_TYPE_RESUME_OP, 1);
+ event.getAttributionChainId(), DiscreteOpsRegistry.ACCESS_TYPE_RESUME_OP, 1);
if (shouldSendActive) {
mAppOpsService.scheduleOpActiveChangedIfNeededLocked(parent.op, parent.uid,
parent.packageName, tag, event.getVirtualDeviceId(), true,
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
new file mode 100644
index 000000000000..e4c36cc214e8
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsDbHelper.java
@@ -0,0 +1,387 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.database.DatabaseErrorHandler;
+import android.database.DefaultDatabaseErrorHandler;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteRawStatement;
+import android.os.Environment;
+import android.util.IntArray;
+import android.util.Slog;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+class DiscreteOpsDbHelper extends SQLiteOpenHelper {
+ private static final String LOG_TAG = "DiscreteOpsDbHelper";
+ static final String DATABASE_NAME = "app_op_history.db";
+ private static final int DATABASE_VERSION = 1;
+ private static final boolean DEBUG = false;
+
+ DiscreteOpsDbHelper(@NonNull Context context, @NonNull File databaseFile) {
+ super(context, databaseFile.getAbsolutePath(), null, DATABASE_VERSION,
+ new DiscreteOpsDatabaseErrorHandler());
+ setOpenParams(getDatabaseOpenParams());
+ }
+
+ private static SQLiteDatabase.OpenParams getDatabaseOpenParams() {
+ return new SQLiteDatabase.OpenParams.Builder()
+ .addOpenFlags(SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING)
+ .build();
+ }
+
+ @NonNull
+ static File getDatabaseFile() {
+ return new File(new File(Environment.getDataSystemDirectory(), "appops"), DATABASE_NAME);
+ }
+
+ @Override
+ public void onConfigure(SQLiteDatabase db) {
+ db.execSQL("PRAGMA synchronous = NORMAL");
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(DiscreteOpsTable.CREATE_TABLE_SQL);
+ db.execSQL(DiscreteOpsTable.CREATE_INDEX_SQL);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ }
+
+ void insertDiscreteOps(@NonNull List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents) {
+ if (opEvents.isEmpty()) {
+ return;
+ }
+
+ SQLiteDatabase db = getWritableDatabase();
+ // TODO (b/383157289) what if database is busy and can't start a transaction? will read
+ // more about it and can be done in a follow up cl.
+ db.beginTransaction();
+ try (SQLiteRawStatement statement = db.createRawStatement(
+ DiscreteOpsTable.INSERT_TABLE_SQL)) {
+ for (DiscreteOpsSqlRegistry.DiscreteOp event : opEvents) {
+ try {
+ statement.bindInt(DiscreteOpsTable.UID_INDEX, event.getUid());
+ bindTextOrNull(statement, DiscreteOpsTable.PACKAGE_NAME_INDEX,
+ event.getPackageName());
+ bindTextOrNull(statement, DiscreteOpsTable.DEVICE_ID_INDEX,
+ event.getDeviceId());
+ statement.bindInt(DiscreteOpsTable.OP_CODE_INDEX, event.getOpCode());
+ bindTextOrNull(statement, DiscreteOpsTable.ATTRIBUTION_TAG_INDEX,
+ event.getAttributionTag());
+ statement.bindLong(DiscreteOpsTable.ACCESS_TIME_INDEX, event.getAccessTime());
+ statement.bindLong(
+ DiscreteOpsTable.ACCESS_DURATION_INDEX, event.getDuration());
+ statement.bindInt(DiscreteOpsTable.UID_STATE_INDEX, event.getUidState());
+ statement.bindInt(DiscreteOpsTable.OP_FLAGS_INDEX, event.getOpFlags());
+ statement.bindInt(DiscreteOpsTable.ATTRIBUTION_FLAGS_INDEX,
+ event.getAttributionFlags());
+ statement.bindLong(DiscreteOpsTable.CHAIN_ID_INDEX, event.getChainId());
+ statement.step();
+ } catch (Exception exception) {
+ Slog.e(LOG_TAG, "Error inserting the discrete op: " + event, exception);
+ } finally {
+ statement.reset();
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ private void bindTextOrNull(SQLiteRawStatement statement, int index, @Nullable String text) {
+ if (text == null) {
+ statement.bindNull(index);
+ } else {
+ statement.bindText(index, text);
+ }
+ }
+
+ /**
+ * This will be used as an offset for inserting new chain id in discrete ops table.
+ */
+ long getLargestAttributionChainId() {
+ long chainId = 0;
+ try {
+ SQLiteDatabase db = getReadableDatabase();
+ db.beginTransactionReadOnly();
+ try (SQLiteRawStatement statement =
+ db.createRawStatement(DiscreteOpsTable.SELECT_MAX_ATTRIBUTION_CHAIN_ID)) {
+ if (statement.step()) {
+ chainId = statement.getColumnLong(0);
+ if (chainId < 0) {
+ chainId = 0;
+ }
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ } catch (SQLiteException exception) {
+ Slog.e(LOG_TAG, "Error reading attribution chain id", exception);
+ }
+ return chainId;
+ }
+
+ void execSQL(@NonNull String sql) {
+ execSQL(sql, null);
+ }
+
+ void execSQL(@NonNull String sql, Object[] bindArgs) {
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "DB execSQL, sql: " + sql);
+ }
+ SQLiteDatabase db = getWritableDatabase();
+ if (bindArgs == null) {
+ db.execSQL(sql);
+ } else {
+ db.execSQL(sql, bindArgs);
+ }
+ }
+
+ /**
+ * Returns a list of {@link DiscreteOpsSqlRegistry.DiscreteOp} based on the given filters.
+ */
+ List<DiscreteOpsSqlRegistry.DiscreteOp> getDiscreteOps(
+ @AppOpsManager.HistoricalOpsRequestFilter int requestFilters,
+ int uidFilter, @Nullable String packageNameFilter,
+ @Nullable String attributionTagFilter, IntArray opCodesFilter, int opFlagsFilter,
+ long beginTime, long endTime, int limit, String orderByColumn) {
+ List<SQLCondition> conditions = prepareConditions(beginTime, endTime, requestFilters,
+ uidFilter, packageNameFilter,
+ attributionTagFilter, opCodesFilter, opFlagsFilter);
+ String sql = buildSql(conditions, orderByColumn, limit);
+
+ SQLiteDatabase db = getReadableDatabase();
+ List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
+ db.beginTransactionReadOnly();
+ try (SQLiteRawStatement statement = db.createRawStatement(sql)) {
+ int size = conditions.size();
+ for (int i = 0; i < size; i++) {
+ SQLCondition condition = conditions.get(i);
+ if (DEBUG) {
+ Slog.i(LOG_TAG, condition + ", binding value = " + condition.mFilterValue);
+ }
+ switch (condition.mColumnFilter) {
+ case PACKAGE_NAME, ATTR_TAG -> statement.bindText(i + 1,
+ condition.mFilterValue.toString());
+ case UID, OP_CODE_EQUAL, OP_FLAGS -> statement.bindInt(i + 1,
+ Integer.parseInt(condition.mFilterValue.toString()));
+ case BEGIN_TIME, END_TIME -> statement.bindLong(i + 1,
+ Long.parseLong(condition.mFilterValue.toString()));
+ case OP_CODE_IN -> Slog.d(LOG_TAG, "No binding for In operator");
+ default -> Slog.w(LOG_TAG, "unknown sql condition " + condition);
+ }
+ }
+
+ while (statement.step()) {
+ int uid = statement.getColumnInt(0);
+ String packageName = statement.getColumnText(1);
+ String deviceId = statement.getColumnText(2);
+ int opCode = statement.getColumnInt(3);
+ String attributionTag = statement.getColumnText(4);
+ long accessTime = statement.getColumnLong(5);
+ long duration = statement.getColumnLong(6);
+ int uidState = statement.getColumnInt(7);
+ int opFlags = statement.getColumnInt(8);
+ int attributionFlags = statement.getColumnInt(9);
+ long chainId = statement.getColumnLong(10);
+ DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+ packageName, attributionTag, deviceId, opCode,
+ opFlags, attributionFlags, uidState, chainId, accessTime, duration);
+ results.add(event);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ return results;
+ }
+
+ private String buildSql(List<SQLCondition> conditions, String orderByColumn, int limit) {
+ StringBuilder sql = new StringBuilder(DiscreteOpsTable.SELECT_TABLE_DATA);
+ if (!conditions.isEmpty()) {
+ sql.append(" WHERE ");
+ int size = conditions.size();
+ for (int i = 0; i < size; i++) {
+ sql.append(conditions.get(i).toString());
+ if (i < size - 1) {
+ sql.append(" AND ");
+ }
+ }
+ }
+
+ if (orderByColumn != null) {
+ sql.append(" ORDER BY ").append(orderByColumn);
+ }
+ if (limit > 0) {
+ sql.append(" LIMIT ").append(limit);
+ }
+ if (DEBUG) {
+ Slog.i(LOG_TAG, "Sql query " + sql);
+ }
+ return sql.toString();
+ }
+
+ /**
+ * Creates where conditions for package, uid, attribution tag and app op codes,
+ * app op codes condition does not support argument binding.
+ */
+ private List<SQLCondition> prepareConditions(long beginTime, long endTime, int requestFilters,
+ int uid, @Nullable String packageName, @Nullable String attributionTag,
+ IntArray opCodes, int opFlags) {
+ final List<SQLCondition> conditions = new ArrayList<>();
+
+ if (beginTime != -1) {
+ conditions.add(new SQLCondition(ColumnFilter.BEGIN_TIME, beginTime));
+ }
+ if (endTime != -1) {
+ conditions.add(new SQLCondition(ColumnFilter.END_TIME, endTime));
+ }
+ if (opFlags != 0) {
+ conditions.add(new SQLCondition(ColumnFilter.OP_FLAGS, opFlags));
+ }
+
+ if (requestFilters != 0) {
+ if ((requestFilters & AppOpsManager.FILTER_BY_PACKAGE_NAME) != 0) {
+ conditions.add(new SQLCondition(ColumnFilter.PACKAGE_NAME, packageName));
+ }
+ if ((requestFilters & AppOpsManager.FILTER_BY_UID) != 0) {
+ conditions.add(new SQLCondition(ColumnFilter.UID, uid));
+
+ }
+ if ((requestFilters & AppOpsManager.FILTER_BY_ATTRIBUTION_TAG) != 0) {
+ conditions.add(new SQLCondition(ColumnFilter.ATTR_TAG, attributionTag));
+ }
+ // filter op codes
+ if (opCodes != null && opCodes.size() == 1) {
+ conditions.add(new SQLCondition(ColumnFilter.OP_CODE_EQUAL, opCodes.get(0)));
+ } else if (opCodes != null && opCodes.size() > 1) {
+ StringBuilder b = new StringBuilder();
+ int size = opCodes.size();
+ for (int i = 0; i < size; i++) {
+ b.append(opCodes.get(i));
+ if (i < size - 1) {
+ b.append(", ");
+ }
+ }
+ conditions.add(new SQLCondition(ColumnFilter.OP_CODE_IN, b.toString()));
+ }
+ }
+ return conditions;
+ }
+
+ /**
+ * This class prepares a where clause condition for discrete ops table column.
+ */
+ static final class SQLCondition {
+ private final ColumnFilter mColumnFilter;
+ private final Object mFilterValue;
+
+ SQLCondition(ColumnFilter columnFilter, Object filterValue) {
+ mColumnFilter = columnFilter;
+ mFilterValue = filterValue;
+ }
+
+ @Override
+ public String toString() {
+ if (mColumnFilter == ColumnFilter.OP_CODE_IN) {
+ return mColumnFilter + " ( " + mFilterValue + " )";
+ }
+ return mColumnFilter.toString();
+ }
+ }
+
+ /**
+ * This enum describes the where clause conditions for different columns in discrete ops
+ * table.
+ */
+ private enum ColumnFilter {
+ PACKAGE_NAME(DiscreteOpsTable.Columns.PACKAGE_NAME + " = ? "),
+ UID(DiscreteOpsTable.Columns.UID + " = ? "),
+ ATTR_TAG(DiscreteOpsTable.Columns.ATTRIBUTION_TAG + " = ? "),
+ END_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " < ? "),
+ OP_CODE_EQUAL(DiscreteOpsTable.Columns.OP_CODE + " = ? "),
+ BEGIN_TIME(DiscreteOpsTable.Columns.ACCESS_TIME + " + "
+ + DiscreteOpsTable.Columns.ACCESS_DURATION + " > ? "),
+ OP_FLAGS("(" + DiscreteOpsTable.Columns.OP_FLAGS + " & ? ) != 0"),
+ OP_CODE_IN(DiscreteOpsTable.Columns.OP_CODE + " IN ");
+
+ final String mCondition;
+
+ ColumnFilter(String condition) {
+ mCondition = condition;
+ }
+
+ @Override
+ public String toString() {
+ return mCondition;
+ }
+ }
+
+ static final class DiscreteOpsDatabaseErrorHandler implements DatabaseErrorHandler {
+ private final DefaultDatabaseErrorHandler mDefaultDatabaseErrorHandler =
+ new DefaultDatabaseErrorHandler();
+
+ @Override
+ public void onCorruption(SQLiteDatabase dbObj) {
+ Slog.e(LOG_TAG, "discrete ops database got corrupted.");
+ mDefaultDatabaseErrorHandler.onCorruption(dbObj);
+ }
+ }
+
+ // USED for testing only
+ List<DiscreteOpsSqlRegistry.DiscreteOp> getAllDiscreteOps(@NonNull String sql) {
+ SQLiteDatabase db = getReadableDatabase();
+ List<DiscreteOpsSqlRegistry.DiscreteOp> results = new ArrayList<>();
+ db.beginTransactionReadOnly();
+ try (SQLiteRawStatement statement = db.createRawStatement(sql)) {
+ while (statement.step()) {
+ int uid = statement.getColumnInt(0);
+ String packageName = statement.getColumnText(1);
+ String deviceId = statement.getColumnText(2);
+ int opCode = statement.getColumnInt(3);
+ String attributionTag = statement.getColumnText(4);
+ long accessTime = statement.getColumnLong(5);
+ long duration = statement.getColumnLong(6);
+ int uidState = statement.getColumnInt(7);
+ int opFlags = statement.getColumnInt(8);
+ int attributionFlags = statement.getColumnInt(9);
+ long chainId = statement.getColumnLong(10);
+ DiscreteOpsSqlRegistry.DiscreteOp event = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+ packageName, attributionTag, deviceId, opCode,
+ opFlags, attributionFlags, uidState, chainId, accessTime, duration);
+ results.add(event);
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ return results;
+ }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
new file mode 100644
index 000000000000..c38ee55b4f42
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsMigrationHelper.java
@@ -0,0 +1,106 @@
+/*
+ * 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.appop;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Helper class for migrating discrete ops from xml to sqlite
+ */
+public class DiscreteOpsMigrationHelper {
+ /**
+ * migrate discrete ops from xml to sqlite.
+ */
+ static void migrateDiscreteOpsToSqlite(DiscreteOpsXmlRegistry xmlRegistry,
+ DiscreteOpsSqlRegistry sqlRegistry) {
+ DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps();
+ List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps = getSqlDiscreteOps(xmlOps);
+ sqlRegistry.migrateXmlData(discreteOps, xmlOps.mChainIdOffset);
+ xmlRegistry.deleteDiscreteOpsDir();
+ }
+
+ /**
+ * rollback discrete ops from sqlite to xml.
+ */
+ static void migrateDiscreteOpsToXml(DiscreteOpsSqlRegistry sqlRegistry,
+ DiscreteOpsXmlRegistry xmlRegistry) {
+ List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps();
+ DiscreteOpsXmlRegistry.DiscreteOps xmlOps = getXmlDiscreteOps(sqlOps);
+ xmlRegistry.migrateSqliteData(xmlOps);
+ sqlRegistry.deleteDatabase();
+ }
+
+ /**
+ * Convert sqlite flat rows to hierarchical data.
+ */
+ private static DiscreteOpsXmlRegistry.DiscreteOps getXmlDiscreteOps(
+ List<DiscreteOpsSqlRegistry.DiscreteOp> discreteOps) {
+ DiscreteOpsXmlRegistry.DiscreteOps xmlOps =
+ new DiscreteOpsXmlRegistry.DiscreteOps(0);
+ if (discreteOps.isEmpty()) {
+ return xmlOps;
+ }
+
+ for (DiscreteOpsSqlRegistry.DiscreteOp discreteOp : discreteOps) {
+ xmlOps.addDiscreteAccess(discreteOp.getOpCode(), discreteOp.getUid(),
+ discreteOp.getPackageName(), discreteOp.getDeviceId(),
+ discreteOp.getAttributionTag(), discreteOp.getOpFlags(),
+ discreteOp.getUidState(),
+ discreteOp.getAccessTime(), discreteOp.getDuration(),
+ discreteOp.getAttributionFlags(), (int) discreteOp.getChainId());
+ }
+ return xmlOps;
+ }
+
+ /**
+ * Convert xml (hierarchical) data to flat row based data.
+ */
+ private static List<DiscreteOpsSqlRegistry.DiscreteOp> getSqlDiscreteOps(
+ DiscreteOpsXmlRegistry.DiscreteOps discreteOps) {
+ List<DiscreteOpsSqlRegistry.DiscreteOp> opEvents = new ArrayList<>();
+
+ if (discreteOps.isEmpty()) {
+ return opEvents;
+ }
+
+ discreteOps.mUids.forEach((uid, discreteUidOps) -> {
+ discreteUidOps.mPackages.forEach((packageName, packageOps) -> {
+ packageOps.mPackageOps.forEach((opcode, ops) -> {
+ ops.mDeviceAttributedOps.forEach((deviceId, deviceOps) -> {
+ deviceOps.mAttributedOps.forEach((tag, attributedOps) -> {
+ for (DiscreteOpsXmlRegistry.DiscreteOpEvent attributedOp :
+ attributedOps) {
+ DiscreteOpsSqlRegistry.DiscreteOp
+ opModel = new DiscreteOpsSqlRegistry.DiscreteOp(uid,
+ packageName, tag,
+ deviceId, opcode, attributedOp.mOpFlag,
+ attributedOp.mAttributionFlags,
+ attributedOp.mUidState, attributedOp.mAttributionChainId,
+ attributedOp.mNoteTime,
+ attributedOp.mNoteDuration);
+ opEvents.add(opModel);
+ }
+ });
+ });
+ });
+ });
+ });
+
+ return opEvents;
+ }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
new file mode 100644
index 000000000000..88b3f6dce4c2
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsRegistry.java
@@ -0,0 +1,298 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.OP_CAMERA;
+import static android.app.AppOpsManager.OP_COARSE_LOCATION;
+import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
+import static android.app.AppOpsManager.OP_FINE_LOCATION;
+import static android.app.AppOpsManager.OP_FLAG_SELF;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
+import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
+import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
+import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
+import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
+import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
+import static android.app.AppOpsManager.OP_READ_ICC_SMS;
+import static android.app.AppOpsManager.OP_READ_SMS;
+import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
+import static android.app.AppOpsManager.OP_SEND_SMS;
+import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
+import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
+import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
+import static android.app.AppOpsManager.OP_WRITE_SMS;
+
+import static java.lang.Long.min;
+import static java.lang.Math.max;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.permission.flags.Flags;
+import android.provider.DeviceConfig;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.Date;
+import java.util.Set;
+
+/**
+ * This class provides interface for xml and sqlite implementation. Implementation manages
+ * information about recent accesses to ops for permission usage timeline.
+ * <p>
+ * The discrete history is kept for limited time (initial default is 24 hours, set in
+ * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that.
+ * <p>
+ * Discrete history is quantized to reduce resources footprint. By default, quantization is set to
+ * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are
+ * aligned to the closest quantized time. All durations (except -1, meaning no duration) are
+ * rounded up to the closest quantized interval.
+ * <p>
+ * When data is queried through API, events are deduplicated and for every time quant there can
+ * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about
+ * different accesses which happened in specified time quant - across dimensions of
+ * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension
+ * it is only possible to know if at least one access happened in the time quant.
+ * <p>
+ * INITIALIZATION: We can initialize persistence only after the system is ready
+ * as we need to check the optional configuration override from the settings
+ * database which is not initialized at the time the app ops service is created. This class
+ * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
+ * outside calls are going through {@link HistoricalRegistry}.
+ *
+ */
+abstract class DiscreteOpsRegistry {
+ private static final String TAG = DiscreteOpsRegistry.class.getSimpleName();
+
+ static final boolean DEBUG_LOG = false;
+ static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
+ static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
+ "discrete_history_quantization_millis";
+ static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
+ static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
+ static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
+ + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
+ + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
+ + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
+ + "," + OP_RESERVED_FOR_TESTING;
+ static final int[] sDiscreteOpsToLog =
+ new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
+ OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
+ OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
+ OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
+ OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
+ OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
+ };
+
+ static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
+ static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
+ // The duration for which the data is kept, default is 7 days and max 30 days enforced.
+ static long sDiscreteHistoryCutoff;
+
+ static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis();
+ // discrete ops are rounded up to quantization time, meaning we record one op per time bucket
+ // in case of duplicate op events.
+ static long sDiscreteHistoryQuantization;
+
+ static int[] sDiscreteOps;
+ static int sDiscreteFlags;
+
+ static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
+ | OP_FLAG_TRUSTED_PROXY;
+
+ boolean mDebugMode = false;
+
+ static final int ACCESS_TYPE_NOTE_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
+ static final int ACCESS_TYPE_START_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
+ static final int ACCESS_TYPE_FINISH_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
+ static final int ACCESS_TYPE_PAUSE_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
+ static final int ACCESS_TYPE_RESUME_OP =
+ FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
+ ACCESS_TYPE_NOTE_OP,
+ ACCESS_TYPE_START_OP,
+ ACCESS_TYPE_FINISH_OP,
+ ACCESS_TYPE_PAUSE_OP,
+ ACCESS_TYPE_RESUME_OP
+ })
+ @interface AccessType {}
+
+ void systemReady() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
+ AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
+ setDiscreteHistoryParameters(p);
+ });
+ setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY));
+ }
+
+ abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId,
+ int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags,
+ @AppOpsManager.UidState int uidState, long accessTime, long accessDuration,
+ @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
+ @DiscreteOpsRegistry.AccessType int accessType);
+
+ /**
+ * A periodic callback from {@link AppOpsService} to flush the in memory events to disk.
+ * The shutdown callback is also plugged into it.
+ * <p>
+ * This method flushes in memory records to disk, and also clears old records from disk.
+ */
+ abstract void writeAndClearOldAccessHistory();
+
+ /** Remove all discrete op events. */
+ abstract void clearHistory();
+
+ /** Remove all discrete op events for given UID and package. */
+ abstract void clearHistory(int uid, String packageName);
+
+ /**
+ * Offset access time by given timestamp, new access time would be accessTime - offsetMillis.
+ */
+ abstract void offsetHistory(long offset);
+
+ abstract void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+ long beginTimeMillis, long endTimeMillis,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
+ @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+ @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter,
+ Set<String> attributionExemptPkgs);
+
+ abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+ @Nullable String attributionTagFilter,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+ int nDiscreteOps);
+
+ void setDebugMode(boolean debugMode) {
+ this.mDebugMode = debugMode;
+ }
+
+ static long discretizeTimeStamp(long timeStamp) {
+ return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+
+ }
+
+ static long discretizeDuration(long duration) {
+ return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
+ / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
+ }
+
+ static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
+ if (!ArrayUtils.contains(sDiscreteOps, op)) {
+ return false;
+ }
+ if ((flags & (sDiscreteFlags)) == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ // could this be impl detail of discrete registry, just one test is using the method
+ // abstract DiscreteRegistry.DiscreteOps getAllDiscreteOps();
+
+ private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
+ if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
+ sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
+ DEFAULT_DISCRETE_HISTORY_CUTOFF);
+ if (!Build.IS_DEBUGGABLE && !mDebugMode) {
+ sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
+ sDiscreteHistoryCutoff);
+ }
+ } else {
+ sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF;
+ }
+ if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
+ sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
+ DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
+ if (!Build.IS_DEBUGGABLE && !mDebugMode) {
+ sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
+ sDiscreteHistoryQuantization);
+ }
+ } else {
+ sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
+ }
+ sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
+ p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
+ sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
+ p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
+ DEFAULT_DISCRETE_OPS);
+ }
+
+ private static int[] parseOpsList(String opsList) {
+ String[] strArr;
+ if (opsList.isEmpty()) {
+ strArr = new String[0];
+ } else {
+ strArr = opsList.split(",");
+ }
+ int nOps = strArr.length;
+ int[] result = new int[nOps];
+ try {
+ for (int i = 0; i < nOps; i++) {
+ result[i] = Integer.parseInt(strArr[i]);
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
+ return parseOpsList(DEFAULT_DISCRETE_OPS);
+ }
+ return result;
+ }
+
+ /**
+ * Whether app op access tacking is enabled and a metric event should be logged.
+ */
+ static boolean shouldLogAccess(int op) {
+ return Flags.appopAccessTrackingLoggingEnabled()
+ && ArrayUtils.contains(sDiscreteOpsToLog, op);
+ }
+
+ String getAttributionTag(String attributionTag, String packageName) {
+ if (attributionTag == null || packageName == null) {
+ return attributionTag;
+ }
+ int firstChar = 0;
+ if (attributionTag.startsWith(packageName)) {
+ firstChar = packageName.length();
+ if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
+ == '.') {
+ firstChar++;
+ }
+ }
+ return attributionTag.substring(firstChar);
+ }
+
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
new file mode 100644
index 000000000000..4b3981cd4bc0
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsSqlRegistry.java
@@ -0,0 +1,689 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.flagsToString;
+import static android.app.AppOpsManager.getUidStateName;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.ArraySet;
+import android.util.IntArray;
+import android.util.LongSparseArray;
+import android.util.Slog;
+
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.ServiceThread;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * This class handles sqlite persistence layer for discrete ops.
+ */
+public class DiscreteOpsSqlRegistry extends DiscreteOpsRegistry {
+ private static final String TAG = "DiscreteOpsSqlRegistry";
+
+ private final Context mContext;
+ private final DiscreteOpsDbHelper mDiscreteOpsDbHelper;
+ private final SqliteWriteHandler mSqliteWriteHandler;
+ private final DiscreteOpCache mDiscreteOpCache = new DiscreteOpCache(512);
+ private static final long THREE_HOURS = Duration.ofHours(3).toMillis();
+ private static final int WRITE_CACHE_EVICTED_OP_EVENTS = 1;
+ private static final int DELETE_OLD_OP_EVENTS = 2;
+ // Attribution chain id is used to identify an attribution source chain, This is
+ // set for startOp only. PermissionManagerService resets this ID on device restart, so
+ // we use previously persisted chain id as offset, and add it to chain id received from
+ // permission manager service.
+ private long mChainIdOffset;
+ private final File mDatabaseFile;
+
+ DiscreteOpsSqlRegistry(Context context) {
+ this(context, DiscreteOpsDbHelper.getDatabaseFile());
+ }
+
+ DiscreteOpsSqlRegistry(Context context, File databaseFile) {
+ ServiceThread thread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true);
+ thread.start();
+ mContext = context;
+ mDatabaseFile = databaseFile;
+ mSqliteWriteHandler = new SqliteWriteHandler(thread.getLooper());
+ mDiscreteOpsDbHelper = new DiscreteOpsDbHelper(context, databaseFile);
+ mChainIdOffset = mDiscreteOpsDbHelper.getLargestAttributionChainId();
+ }
+
+ @Override
+ void recordDiscreteAccess(int uid, String packageName,
+ @NonNull String deviceId, int op,
+ @Nullable String attributionTag, int flags, int uidState,
+ long accessTime, long accessDuration, int attributionFlags, int attributionChainId,
+ int accessType) {
+ if (shouldLogAccess(op)) {
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
+ uidState, flags, attributionFlags,
+ getAttributionTag(attributionTag, packageName),
+ attributionChainId);
+ }
+
+ if (!isDiscreteOp(op, flags)) {
+ return;
+ }
+
+ long offsetChainId = attributionChainId;
+ if (attributionChainId != ATTRIBUTION_CHAIN_ID_NONE) {
+ offsetChainId = attributionChainId + mChainIdOffset;
+ // PermissionManagerService chain id reached the max value,
+ // reset offset, it's going to be very rare.
+ if (attributionChainId == Integer.MAX_VALUE) {
+ mChainIdOffset = offsetChainId;
+ }
+ }
+ DiscreteOp discreteOpEvent = new DiscreteOp(uid, packageName, attributionTag, deviceId, op,
+ flags, attributionFlags, uidState, offsetChainId, accessTime, accessDuration);
+ mDiscreteOpCache.add(discreteOpEvent);
+ }
+
+ @Override
+ void writeAndClearOldAccessHistory() {
+ // Let the sql impl also follow the same disk write frequencies as xml,
+ // controlled by AppOpsService.
+ mDiscreteOpsDbHelper.insertDiscreteOps(mDiscreteOpCache.getAllEventsAndClear());
+ if (!mSqliteWriteHandler.hasMessages(DELETE_OLD_OP_EVENTS)) {
+ if (mSqliteWriteHandler.sendEmptyMessageDelayed(DELETE_OLD_OP_EVENTS, THREE_HOURS)) {
+ Slog.w(TAG, "DELETE_OLD_OP_EVENTS is not queued");
+ }
+ }
+ }
+
+ @Override
+ void clearHistory() {
+ mDiscreteOpCache.clear();
+ mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_TABLE_DATA);
+ }
+
+ @Override
+ void clearHistory(int uid, String packageName) {
+ mDiscreteOpCache.clear(uid, packageName);
+ mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.DELETE_DATA_FOR_UID_PACKAGE,
+ new Object[]{uid, packageName});
+ }
+
+ @Override
+ void offsetHistory(long offset) {
+ mDiscreteOpCache.offsetTimestamp(offset);
+ mDiscreteOpsDbHelper.execSQL(DiscreteOpsTable.OFFSET_ACCESS_TIME,
+ new Object[]{offset});
+ }
+
+ private IntArray getAppOpCodes(@AppOpsManager.HistoricalOpsRequestFilter int filter,
+ @Nullable String[] opNamesFilter) {
+ if ((filter & AppOpsManager.FILTER_BY_OP_NAMES) != 0) {
+ IntArray opCodes = new IntArray(opNamesFilter.length);
+ for (int i = 0; i < opNamesFilter.length; i++) {
+ int op;
+ try {
+ op = AppOpsManager.strOpToOp(opNamesFilter[i]);
+ } catch (IllegalArgumentException ex) {
+ Slog.w(TAG, "Appop `" + opNamesFilter[i] + "` is not recognized.");
+ continue;
+ }
+ opCodes.add(op);
+ }
+ return opCodes;
+ }
+ return null;
+ }
+
+ @Override
+ void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+ long beginTimeMillis, long endTimeMillis, int filter, int uidFilter,
+ @Nullable String packageNameFilter,
+ @Nullable String[] opNamesFilter,
+ @Nullable String attributionTagFilter, int opFlagsFilter,
+ Set<String> attributionExemptPkgs) {
+ // flush the cache into database before read.
+ writeAndClearOldAccessHistory();
+ boolean assembleChains = attributionExemptPkgs != null;
+ IntArray opCodes = getAppOpCodes(filter, opNamesFilter);
+ List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
+ packageNameFilter, attributionTagFilter, opCodes, opFlagsFilter, beginTimeMillis,
+ endTimeMillis, -1, null);
+
+ LongSparseArray<AttributionChain> attributionChains = null;
+ if (assembleChains) {
+ attributionChains = createAttributionChains(discreteOps, attributionExemptPkgs);
+ }
+
+ int nEvents = discreteOps.size();
+ for (int j = 0; j < nEvents; j++) {
+ DiscreteOp event = discreteOps.get(j);
+ AppOpsManager.OpEventProxyInfo proxy = null;
+ if (assembleChains && event.mChainId != ATTRIBUTION_CHAIN_ID_NONE) {
+ AttributionChain chain = attributionChains.get(event.mChainId);
+ if (chain != null && chain.isComplete()
+ && chain.isStart(event)
+ && chain.mLastVisibleEvent != null) {
+ DiscreteOp proxyEvent = chain.mLastVisibleEvent;
+ proxy = new AppOpsManager.OpEventProxyInfo(proxyEvent.mUid,
+ proxyEvent.mPackageName, proxyEvent.mAttributionTag);
+ }
+ }
+ result.addDiscreteAccess(event.mOpCode, event.mUid, event.mPackageName,
+ event.mAttributionTag, event.mUidState, event.mOpFlags,
+ event.mDiscretizedAccessTime, event.mDiscretizedDuration, proxy);
+ }
+ }
+
+ @Override
+ void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+ @Nullable String attributionTagFilter,
+ @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+ int nDiscreteOps) {
+ writeAndClearOldAccessHistory();
+ IntArray opCodes = new IntArray();
+ if (dumpOp != AppOpsManager.OP_NONE) {
+ opCodes.add(dumpOp);
+ }
+ List<DiscreteOp> discreteOps = mDiscreteOpsDbHelper.getDiscreteOps(filter, uidFilter,
+ packageNameFilter, attributionTagFilter, opCodes, 0, -1,
+ -1, nDiscreteOps, DiscreteOpsTable.Columns.ACCESS_TIME);
+
+ pw.print(prefix);
+ pw.print("Largest chain id: ");
+ pw.print(mDiscreteOpsDbHelper.getLargestAttributionChainId());
+ pw.println();
+ pw.println("UID|PACKAGE_NAME|DEVICE_ID|OP_NAME|ATTRIBUTION_TAG|UID_STATE|OP_FLAGS|"
+ + "ATTR_FLAGS|CHAIN_ID|ACCESS_TIME|DURATION");
+ int discreteOpsCount = discreteOps.size();
+ for (int i = 0; i < discreteOpsCount; i++) {
+ DiscreteOp event = discreteOps.get(i);
+ date.setTime(event.mAccessTime);
+ pw.println(event.mUid + "|" + event.mPackageName + "|" + event.mDeviceId + "|"
+ + AppOpsManager.opToName(event.mOpCode) + "|" + event.mAttributionTag + "|"
+ + getUidStateName(event.mUidState) + "|"
+ + flagsToString(event.mOpFlags) + "|" + event.mAttributionFlags + "|"
+ + event.mChainId + "|"
+ + sdf.format(date) + "|" + event.mDuration);
+ }
+ pw.println();
+ }
+
+ void migrateXmlData(List<DiscreteOp> opEvents, int chainIdOffset) {
+ mChainIdOffset = chainIdOffset;
+ mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
+ }
+
+ LongSparseArray<AttributionChain> createAttributionChains(
+ List<DiscreteOp> discreteOps, Set<String> attributionExemptPkgs) {
+ LongSparseArray<AttributionChain> chains = new LongSparseArray<>();
+ final int count = discreteOps.size();
+
+ for (int i = 0; i < count; i++) {
+ DiscreteOp opEvent = discreteOps.get(i);
+ if (opEvent.mChainId == ATTRIBUTION_CHAIN_ID_NONE
+ || (opEvent.mAttributionFlags & ATTRIBUTION_FLAG_TRUSTED) == 0) {
+ continue;
+ }
+ AttributionChain chain = chains.get(opEvent.mChainId);
+ if (chain == null) {
+ chain = new AttributionChain(attributionExemptPkgs);
+ chains.put(opEvent.mChainId, chain);
+ }
+ chain.addEvent(opEvent);
+ }
+ return chains;
+ }
+
+ static class AttributionChain {
+ List<DiscreteOp> mChain = new ArrayList<>();
+ Set<String> mExemptPkgs;
+ DiscreteOp mStartEvent = null;
+ DiscreteOp mLastVisibleEvent = null;
+
+ AttributionChain(Set<String> exemptPkgs) {
+ mExemptPkgs = exemptPkgs;
+ }
+
+ boolean isComplete() {
+ return !mChain.isEmpty() && getStart() != null && isEnd(mChain.get(mChain.size() - 1));
+ }
+
+ DiscreteOp getStart() {
+ return mChain.isEmpty() || !isStart(mChain.get(0)) ? null : mChain.get(0);
+ }
+
+ private boolean isEnd(DiscreteOp event) {
+ return event != null
+ && (event.mAttributionFlags & ATTRIBUTION_FLAG_ACCESSOR) != 0;
+ }
+
+ private boolean isStart(DiscreteOp event) {
+ return event != null
+ && (event.mAttributionFlags & ATTRIBUTION_FLAG_RECEIVER) != 0;
+ }
+
+ DiscreteOp getLastVisible() {
+ // Search all nodes but the first one, which is the start node
+ for (int i = mChain.size() - 1; i > 0; i--) {
+ DiscreteOp event = mChain.get(i);
+ if (!mExemptPkgs.contains(event.mPackageName)) {
+ return event;
+ }
+ }
+ return null;
+ }
+
+ void addEvent(DiscreteOp opEvent) {
+ // check if we have a matching event except duration.
+ DiscreteOp matchingItem = null;
+ for (int i = 0; i < mChain.size(); i++) {
+ DiscreteOp item = mChain.get(i);
+ if (item.equalsExceptDuration(opEvent)) {
+ matchingItem = item;
+ break;
+ }
+ }
+
+ if (matchingItem != null) {
+ // exact match or existing event has longer duration
+ if (matchingItem.mDuration == opEvent.mDuration
+ || matchingItem.mDuration > opEvent.mDuration) {
+ return;
+ }
+ mChain.remove(matchingItem);
+ }
+
+ if (mChain.isEmpty() || isEnd(opEvent)) {
+ mChain.add(opEvent);
+ } else if (isStart(opEvent)) {
+ mChain.add(0, opEvent);
+ } else {
+ for (int i = 0; i < mChain.size(); i++) {
+ DiscreteOp currEvent = mChain.get(i);
+ if ((!isStart(currEvent)
+ && currEvent.mAccessTime > opEvent.mAccessTime)
+ || (i == mChain.size() - 1 && isEnd(currEvent))) {
+ mChain.add(i, opEvent);
+ break;
+ } else if (i == mChain.size() - 1) {
+ mChain.add(opEvent);
+ break;
+ }
+ }
+ }
+ mStartEvent = isComplete() ? getStart() : null;
+ mLastVisibleEvent = isComplete() ? getLastVisible() : null;
+ }
+ }
+
+ /**
+ * Handler to write asynchronously to sqlite database.
+ */
+ class SqliteWriteHandler extends Handler {
+ SqliteWriteHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case WRITE_CACHE_EVICTED_OP_EVENTS:
+ List<DiscreteOp> opEvents = (List<DiscreteOp>) msg.obj;
+ mDiscreteOpsDbHelper.insertDiscreteOps(opEvents);
+ break;
+ case DELETE_OLD_OP_EVENTS:
+ long cutOffTimeStamp = System.currentTimeMillis() - sDiscreteHistoryCutoff;
+ mDiscreteOpsDbHelper.execSQL(
+ DiscreteOpsTable.DELETE_TABLE_DATA_BEFORE_ACCESS_TIME,
+ new Object[]{cutOffTimeStamp});
+ break;
+ default:
+ throw new IllegalStateException("Unexpected value: " + msg.what);
+ }
+ }
+ }
+
+ /**
+ * A write cache for discrete ops. The noteOp, start/finishOp discrete op events are written to
+ * the cache first.
+ * <p>
+ * These events are persisted into sqlite database
+ * 1) Periodic interval, controlled by {@link AppOpsService}
+ * 2) When total events in the cache exceeds cache limit.
+ * 3) During read call we flush the whole cache to sqlite.
+ * 4) During shutdown.
+ */
+ class DiscreteOpCache {
+ private final int mCapacity;
+ private final ArraySet<DiscreteOp> mCache;
+
+ DiscreteOpCache(int capacity) {
+ mCapacity = capacity;
+ mCache = new ArraySet<>();
+ }
+
+ public void add(DiscreteOp opEvent) {
+ synchronized (this) {
+ if (mCache.contains(opEvent)) {
+ return;
+ }
+ mCache.add(opEvent);
+ if (mCache.size() >= mCapacity) {
+ if (DEBUG_LOG) {
+ Slog.i(TAG, "Current discrete ops cache size: " + mCache.size());
+ }
+ List<DiscreteOp> evictedEvents = evict();
+ if (DEBUG_LOG) {
+ Slog.i(TAG, "Evicted discrete ops size: " + evictedEvents.size());
+ }
+ // if nothing to evict, just write the whole cache to disk
+ if (evictedEvents.isEmpty()) {
+ Slog.w(TAG, "No discrete ops event is evicted, write cache to db.");
+ evictedEvents.addAll(mCache);
+ mCache.clear();
+ }
+ mSqliteWriteHandler.obtainMessage(WRITE_CACHE_EVICTED_OP_EVENTS, evictedEvents);
+ }
+ }
+ }
+
+ /**
+ * Evict entries older than {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}.
+ */
+ private List<DiscreteOp> evict() {
+ synchronized (this) {
+ List<DiscreteOp> evictedEvents = new ArrayList<>();
+ Set<DiscreteOp> snapshot = new ArraySet<>(mCache);
+ long evictionTimestamp = System.currentTimeMillis() - sDiscreteHistoryQuantization;
+ evictionTimestamp = discretizeTimeStamp(evictionTimestamp);
+ for (DiscreteOp opEvent : snapshot) {
+ if (opEvent.mDiscretizedAccessTime <= evictionTimestamp) {
+ evictedEvents.add(opEvent);
+ mCache.remove(opEvent);
+ }
+ }
+ return evictedEvents;
+ }
+ }
+
+ /**
+ * Remove all the entries from cache.
+ *
+ * @return return all removed entries.
+ */
+ public List<DiscreteOp> getAllEventsAndClear() {
+ synchronized (this) {
+ List<DiscreteOp> cachedOps = new ArrayList<>(mCache.size());
+ if (mCache.isEmpty()) {
+ return cachedOps;
+ }
+ cachedOps.addAll(mCache);
+ mCache.clear();
+ return cachedOps;
+ }
+ }
+
+ /**
+ * Remove all entries from the cache.
+ */
+ public void clear() {
+ synchronized (this) {
+ mCache.clear();
+ }
+ }
+
+ /**
+ * Offset access time by given offset milliseconds.
+ */
+ public void offsetTimestamp(long offsetMillis) {
+ synchronized (this) {
+ List<DiscreteOp> cachedOps = new ArrayList<>(mCache);
+ mCache.clear();
+ for (DiscreteOp discreteOp : cachedOps) {
+ add(new DiscreteOp(discreteOp.getUid(), discreteOp.mPackageName,
+ discreteOp.getAttributionTag(), discreteOp.getDeviceId(),
+ discreteOp.mOpCode, discreteOp.mOpFlags,
+ discreteOp.getAttributionFlags(), discreteOp.getUidState(),
+ discreteOp.getChainId(), discreteOp.mAccessTime - offsetMillis,
+ discreteOp.getDuration())
+ );
+ }
+ }
+ }
+
+ /** Remove cached events for given UID and package. */
+ public void clear(int uid, String packageName) {
+ synchronized (this) {
+ Set<DiscreteOp> snapshot = new ArraySet<>(mCache);
+ for (DiscreteOp currentEvent : snapshot) {
+ if (Objects.equals(packageName, currentEvent.mPackageName)
+ && uid == currentEvent.getUid()) {
+ mCache.remove(currentEvent);
+ }
+ }
+ }
+ }
+ }
+
+ /** Immutable discrete op object. */
+ static class DiscreteOp {
+ private final int mUid;
+ private final String mPackageName;
+ private final String mAttributionTag;
+ private final String mDeviceId;
+ private final int mOpCode;
+ private final int mOpFlags;
+ private final int mAttributionFlags;
+ private final int mUidState;
+ private final long mChainId;
+ private final long mAccessTime;
+ private final long mDuration;
+ // store discretized timestamp to avoid repeated calculations.
+ private final long mDiscretizedAccessTime;
+ private final long mDiscretizedDuration;
+
+ DiscreteOp(int uid, String packageName, String attributionTag, String deviceId,
+ int opCode,
+ int mOpFlags, int mAttributionFlags, int uidState, long chainId, long accessTime,
+ long duration) {
+ this.mUid = uid;
+ this.mPackageName = packageName.intern();
+ this.mAttributionTag = attributionTag;
+ this.mDeviceId = deviceId;
+ this.mOpCode = opCode;
+ this.mOpFlags = mOpFlags;
+ this.mAttributionFlags = mAttributionFlags;
+ this.mUidState = uidState;
+ this.mChainId = chainId;
+ this.mAccessTime = accessTime;
+ this.mDiscretizedAccessTime = discretizeTimeStamp(accessTime);
+ this.mDuration = duration;
+ this.mDiscretizedDuration = discretizeDuration(duration);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof DiscreteOp that)) return false;
+
+ if (mUid != that.mUid) return false;
+ if (mOpCode != that.mOpCode) return false;
+ if (mOpFlags != that.mOpFlags) return false;
+ if (mAttributionFlags != that.mAttributionFlags) return false;
+ if (mUidState != that.mUidState) return false;
+ if (mChainId != that.mChainId) return false;
+ if (!Objects.equals(mPackageName, that.mPackageName)) {
+ return false;
+ }
+ if (!Objects.equals(mAttributionTag, that.mAttributionTag)) {
+ return false;
+ }
+ if (!Objects.equals(mDeviceId, that.mDeviceId)) {
+ return false;
+ }
+ if (mDiscretizedAccessTime != that.mDiscretizedAccessTime) {
+ return false;
+ }
+ return mDiscretizedDuration == that.mDiscretizedDuration;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mUid;
+ result = 31 * result + (mPackageName != null ? mPackageName.hashCode() : 0);
+ result = 31 * result + (mAttributionTag != null ? mAttributionTag.hashCode() : 0);
+ result = 31 * result + (mDeviceId != null ? mDeviceId.hashCode() : 0);
+ result = 31 * result + mOpCode;
+ result = 31 * result + mOpFlags;
+ result = 31 * result + mAttributionFlags;
+ result = 31 * result + mUidState;
+ result = 31 * result + Objects.hash(mChainId);
+ result = 31 * result + Objects.hash(mDiscretizedAccessTime);
+ result = 31 * result + Objects.hash(mDiscretizedDuration);
+ return result;
+ }
+
+ public boolean equalsExceptDuration(DiscreteOp that) {
+ if (mUid != that.mUid) return false;
+ if (mOpCode != that.mOpCode) return false;
+ if (mOpFlags != that.mOpFlags) return false;
+ if (mAttributionFlags != that.mAttributionFlags) return false;
+ if (mUidState != that.mUidState) return false;
+ if (mChainId != that.mChainId) return false;
+ if (!Objects.equals(mPackageName, that.mPackageName)) {
+ return false;
+ }
+ if (!Objects.equals(mAttributionTag, that.mAttributionTag)) {
+ return false;
+ }
+ if (!Objects.equals(mDeviceId, that.mDeviceId)) {
+ return false;
+ }
+ return mAccessTime == that.mAccessTime;
+ }
+
+ @Override
+ public String toString() {
+ return "DiscreteOp{"
+ + "uid=" + mUid
+ + ", packageName='" + mPackageName + '\''
+ + ", attributionTag='" + mAttributionTag + '\''
+ + ", deviceId='" + mDeviceId + '\''
+ + ", opCode=" + AppOpsManager.opToName(mOpCode)
+ + ", opFlag=" + flagsToString(mOpFlags)
+ + ", attributionFlag=" + mAttributionFlags
+ + ", uidState=" + getUidStateName(mUidState)
+ + ", chainId=" + mChainId
+ + ", accessTime=" + mAccessTime
+ + ", duration=" + mDuration + '}';
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ public String getAttributionTag() {
+ return mAttributionTag;
+ }
+
+ public String getDeviceId() {
+ return mDeviceId;
+ }
+
+ public int getOpCode() {
+ return mOpCode;
+ }
+
+ @AppOpsManager.OpFlags
+ public int getOpFlags() {
+ return mOpFlags;
+ }
+
+
+ @AppOpsManager.AttributionFlags
+ public int getAttributionFlags() {
+ return mAttributionFlags;
+ }
+
+ @AppOpsManager.UidState
+ public int getUidState() {
+ return mUidState;
+ }
+
+ public long getChainId() {
+ return mChainId;
+ }
+
+ public long getAccessTime() {
+ return mAccessTime;
+ }
+
+ public long getDuration() {
+ return mDuration;
+ }
+ }
+
+ // API for tests only, can be removed or changed.
+ void recordDiscreteAccess(DiscreteOp discreteOpEvent) {
+ mDiscreteOpCache.add(discreteOpEvent);
+ }
+
+ // API for tests only, can be removed or changed.
+ List<DiscreteOp> getCachedDiscreteOps() {
+ return new ArrayList<>(mDiscreteOpCache.mCache);
+ }
+
+ // API for tests only, can be removed or changed.
+ List<DiscreteOp> getAllDiscreteOps() {
+ List<DiscreteOp> ops = new ArrayList<>(mDiscreteOpCache.mCache);
+ ops.addAll(mDiscreteOpsDbHelper.getAllDiscreteOps(DiscreteOpsTable.SELECT_TABLE_DATA));
+ return ops;
+ }
+
+ // API for testing and migration
+ long getLargestAttributionChainId() {
+ return mDiscreteOpsDbHelper.getLargestAttributionChainId();
+ }
+
+ // API for testing and migration
+ void deleteDatabase() {
+ mDiscreteOpsDbHelper.close();
+ mContext.deleteDatabase(mDatabaseFile.getName());
+ }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTable.java b/services/core/java/com/android/server/appop/DiscreteOpsTable.java
new file mode 100644
index 000000000000..9cb19aa30a15
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsTable.java
@@ -0,0 +1,128 @@
+/*
+ * 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.appop;
+
+
+/**
+ * SQLite table for storing app op accesses.
+ */
+final class DiscreteOpsTable {
+ private static final String TABLE_NAME = "app_op_accesses";
+ private static final String INDEX_APP_OP = "app_op_access_index";
+
+ static final class Columns {
+ /** Auto increment primary key. */
+ static final String ID = "id";
+ /** UID of the package accessing private data. */
+ static final String UID = "uid";
+ /** Package accessing private data. */
+ static final String PACKAGE_NAME = "package_name";
+ /** The device from which the private data is accessed. */
+ static final String DEVICE_ID = "device_id";
+ /** Op code representing private data i.e. location, mic etc. */
+ static final String OP_CODE = "op_code";
+ /** Attribution tag provided when accessing the private data. */
+ static final String ATTRIBUTION_TAG = "attribution_tag";
+ /** Timestamp when private data is accessed, number of milliseconds that have passed
+ * since Unix epoch */
+ static final String ACCESS_TIME = "access_time";
+ /** For how long the private data is accessed. */
+ static final String ACCESS_DURATION = "access_duration";
+ /** App process state, whether the app is in foreground, background or cached etc. */
+ static final String UID_STATE = "uid_state";
+ /** App op flags */
+ static final String OP_FLAGS = "op_flags";
+ /** Attribution flags */
+ static final String ATTRIBUTION_FLAGS = "attribution_flags";
+ /** Chain id */
+ static final String CHAIN_ID = "chain_id";
+ }
+
+ static final int UID_INDEX = 1;
+ static final int PACKAGE_NAME_INDEX = 2;
+ static final int DEVICE_ID_INDEX = 3;
+ static final int OP_CODE_INDEX = 4;
+ static final int ATTRIBUTION_TAG_INDEX = 5;
+ static final int ACCESS_TIME_INDEX = 6;
+ static final int ACCESS_DURATION_INDEX = 7;
+ static final int UID_STATE_INDEX = 8;
+ static final int OP_FLAGS_INDEX = 9;
+ static final int ATTRIBUTION_FLAGS_INDEX = 10;
+ static final int CHAIN_ID_INDEX = 11;
+
+ static final String CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS "
+ + TABLE_NAME + "("
+ + Columns.ID + " INTEGER PRIMARY KEY,"
+ + Columns.UID + " INTEGER,"
+ + Columns.PACKAGE_NAME + " TEXT,"
+ + Columns.DEVICE_ID + " TEXT NOT NULL,"
+ + Columns.OP_CODE + " INTEGER,"
+ + Columns.ATTRIBUTION_TAG + " TEXT,"
+ + Columns.ACCESS_TIME + " INTEGER,"
+ + Columns.ACCESS_DURATION + " INTEGER,"
+ + Columns.UID_STATE + " INTEGER,"
+ + Columns.OP_FLAGS + " INTEGER,"
+ + Columns.ATTRIBUTION_FLAGS + " INTEGER,"
+ + Columns.CHAIN_ID + " INTEGER"
+ + ")";
+
+ static final String INSERT_TABLE_SQL = "INSERT INTO " + TABLE_NAME + "("
+ + Columns.UID + ", "
+ + Columns.PACKAGE_NAME + ", "
+ + Columns.DEVICE_ID + ", "
+ + Columns.OP_CODE + ", "
+ + Columns.ATTRIBUTION_TAG + ", "
+ + Columns.ACCESS_TIME + ", "
+ + Columns.ACCESS_DURATION + ", "
+ + Columns.UID_STATE + ", "
+ + Columns.OP_FLAGS + ", "
+ + Columns.ATTRIBUTION_FLAGS + ", "
+ + Columns.CHAIN_ID + ") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+
+ static final String SELECT_MAX_ATTRIBUTION_CHAIN_ID = "SELECT MAX(" + Columns.CHAIN_ID + ")"
+ + " FROM " + TABLE_NAME;
+
+ static final String SELECT_TABLE_DATA = "SELECT DISTINCT "
+ + Columns.UID + ","
+ + Columns.PACKAGE_NAME + ","
+ + Columns.DEVICE_ID + ","
+ + Columns.OP_CODE + ","
+ + Columns.ATTRIBUTION_TAG + ","
+ + Columns.ACCESS_TIME + ","
+ + Columns.ACCESS_DURATION + ","
+ + Columns.UID_STATE + ","
+ + Columns.OP_FLAGS + ","
+ + Columns.ATTRIBUTION_FLAGS + ","
+ + Columns.CHAIN_ID
+ + " FROM " + TABLE_NAME;
+
+ static final String DELETE_TABLE_DATA = "DELETE FROM " + TABLE_NAME;
+
+ static final String DELETE_TABLE_DATA_BEFORE_ACCESS_TIME = "DELETE FROM " + TABLE_NAME
+ + " WHERE " + Columns.ACCESS_TIME + " < ?";
+
+ static final String DELETE_DATA_FOR_UID_PACKAGE = "DELETE FROM " + DiscreteOpsTable.TABLE_NAME
+ + " WHERE " + Columns.UID + " = ? AND " + Columns.PACKAGE_NAME + " = ?";
+
+ static final String OFFSET_ACCESS_TIME = "UPDATE " + DiscreteOpsTable.TABLE_NAME
+ + " SET " + Columns.ACCESS_TIME + " = ACCESS_TIME - ?";
+
+ // Index on access time, uid and op code
+ static final String CREATE_INDEX_SQL = "CREATE INDEX IF NOT EXISTS "
+ + INDEX_APP_OP + " ON " + TABLE_NAME
+ + " (" + Columns.ACCESS_TIME + ", " + Columns.UID + ", " + Columns.OP_CODE + ")";
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
new file mode 100644
index 000000000000..1523cca86607
--- /dev/null
+++ b/services/core/java/com/android/server/appop/DiscreteOpsTestingShim.java
@@ -0,0 +1,220 @@
+/*
+ * 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.appop;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A testing class, which supports both xml and sqlite persistence for discrete ops, the class
+ * logs warning if there is a mismatch in the behavior.
+ */
+class DiscreteOpsTestingShim extends DiscreteOpsRegistry {
+ private static final String LOG_TAG = "DiscreteOpsTestingShim";
+ private final DiscreteOpsRegistry mXmlRegistry;
+ private final DiscreteOpsRegistry mSqlRegistry;
+
+ DiscreteOpsTestingShim(DiscreteOpsRegistry xmlRegistry,
+ DiscreteOpsRegistry sqlRegistry) {
+ mXmlRegistry = xmlRegistry;
+ mSqlRegistry = sqlRegistry;
+ }
+
+ @Override
+ void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
+ @Nullable String attributionTag, int flags, int uidState, long accessTime,
+ long accessDuration, int attributionFlags, int attributionChainId, int accessType) {
+ long start = SystemClock.uptimeMillis();
+ mXmlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
+ uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
+ accessType);
+ long start2 = SystemClock.uptimeMillis();
+ mSqlRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, attributionTag, flags,
+ uidState, accessTime, accessDuration, attributionFlags, attributionChainId,
+ accessType);
+ long end = SystemClock.uptimeMillis();
+ long xmlTimeTaken = start2 - start;
+ long sqlTimeTaken = end - start2;
+ Log.i(LOG_TAG,
+ "recordDiscreteAccess: XML time taken : " + xmlTimeTaken + ", SQL time taken : "
+ + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken));
+ }
+
+
+ @Override
+ void writeAndClearOldAccessHistory() {
+ mXmlRegistry.writeAndClearOldAccessHistory();
+ mSqlRegistry.writeAndClearOldAccessHistory();
+ }
+
+ @Override
+ void clearHistory() {
+ mXmlRegistry.clearHistory();
+ mSqlRegistry.clearHistory();
+ }
+
+ @Override
+ void clearHistory(int uid, String packageName) {
+ mXmlRegistry.clearHistory(uid, packageName);
+ mSqlRegistry.clearHistory(uid, packageName);
+ }
+
+ @Override
+ void offsetHistory(long offset) {
+ mXmlRegistry.offsetHistory(offset);
+ mSqlRegistry.offsetHistory(offset);
+ }
+
+ @Override
+ void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
+ long beginTimeMillis, long endTimeMillis, int filter, int uidFilter,
+ @Nullable String packageNameFilter, @Nullable String[] opNamesFilter,
+ @Nullable String attributionTagFilter, int flagsFilter,
+ Set<String> attributionExemptPkgs) {
+ AppOpsManager.HistoricalOps result2 =
+ new AppOpsManager.HistoricalOps(beginTimeMillis, endTimeMillis);
+
+ long start = System.currentTimeMillis();
+ mXmlRegistry.addFilteredDiscreteOpsToHistoricalOps(result2, beginTimeMillis, endTimeMillis,
+ filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
+ flagsFilter, attributionExemptPkgs);
+ long start2 = System.currentTimeMillis();
+ mSqlRegistry.addFilteredDiscreteOpsToHistoricalOps(result, beginTimeMillis, endTimeMillis,
+ filter, uidFilter, packageNameFilter, opNamesFilter, attributionTagFilter,
+ flagsFilter, attributionExemptPkgs);
+ long end = System.currentTimeMillis();
+ long xmlTimeTaken = start2 - start;
+ long sqlTimeTaken = end - start2;
+ try {
+ assertHistoricalOpsAreEquals(result, result2);
+ } catch (Exception ex) {
+ Slog.e(LOG_TAG, "different output when reading discrete ops", ex);
+ }
+ Log.i(LOG_TAG, "Read: XML time taken : " + xmlTimeTaken + ", SQL time taken : "
+ + sqlTimeTaken + ", diff (sql - xml): " + (sqlTimeTaken - xmlTimeTaken));
+ }
+
+ void assertHistoricalOpsAreEquals(AppOpsManager.HistoricalOps sqlResult,
+ AppOpsManager.HistoricalOps xmlResult) {
+ assertEquals(sqlResult.getUidCount(), xmlResult.getUidCount());
+ int uidCount = sqlResult.getUidCount();
+
+ for (int i = 0; i < uidCount; i++) {
+ AppOpsManager.HistoricalUidOps sqlUidOps = sqlResult.getUidOpsAt(i);
+ AppOpsManager.HistoricalUidOps xmlUidOps = xmlResult.getUidOpsAt(i);
+ Slog.i(LOG_TAG, "sql uid: " + sqlUidOps.getUid() + ", xml uid: " + xmlUidOps.getUid());
+ assertEquals(sqlUidOps.getUid(), xmlUidOps.getUid());
+ assertEquals(sqlUidOps.getPackageCount(), xmlUidOps.getPackageCount());
+
+ int packageCount = sqlUidOps.getPackageCount();
+ for (int p = 0; p < packageCount; p++) {
+ AppOpsManager.HistoricalPackageOps sqlPackageOps = sqlUidOps.getPackageOpsAt(p);
+ AppOpsManager.HistoricalPackageOps xmlPackageOps = xmlUidOps.getPackageOpsAt(p);
+ Slog.i(LOG_TAG, "sql package: " + sqlPackageOps.getPackageName() + ", xml package: "
+ + xmlPackageOps.getPackageName());
+ assertEquals(sqlPackageOps.getPackageName(), xmlPackageOps.getPackageName());
+ assertEquals(sqlPackageOps.getAttributedOpsCount(),
+ xmlPackageOps.getAttributedOpsCount());
+
+ int attrCount = sqlPackageOps.getAttributedOpsCount();
+ for (int a = 0; a < attrCount; a++) {
+ AppOpsManager.AttributedHistoricalOps sqlAttrOps =
+ sqlPackageOps.getAttributedOpsAt(a);
+ AppOpsManager.AttributedHistoricalOps xmlAttrOps =
+ xmlPackageOps.getAttributedOpsAt(a);
+ Slog.i(LOG_TAG, "sql tag: " + sqlAttrOps.getTag() + ", xml tag: "
+ + xmlAttrOps.getTag());
+ assertEquals(sqlAttrOps.getTag(), xmlAttrOps.getTag());
+ assertEquals(sqlAttrOps.getOpCount(), xmlAttrOps.getOpCount());
+
+ int opCount = sqlAttrOps.getOpCount();
+ for (int o = 0; o < opCount; o++) {
+ AppOpsManager.HistoricalOp sqlHistoricalOp = sqlAttrOps.getOpAt(o);
+ AppOpsManager.HistoricalOp xmlHistoricalOp = xmlAttrOps.getOpAt(o);
+ Slog.i(LOG_TAG, "sql op: " + sqlHistoricalOp.getOpName() + ", xml op: "
+ + xmlHistoricalOp.getOpName());
+ assertEquals(sqlHistoricalOp.getOpName(), xmlHistoricalOp.getOpName());
+ assertEquals(sqlHistoricalOp.getDiscreteAccessCount(),
+ xmlHistoricalOp.getDiscreteAccessCount());
+
+ int accessCount = sqlHistoricalOp.getDiscreteAccessCount();
+ for (int x = 0; x < accessCount; x++) {
+ AppOpsManager.AttributedOpEntry sqlOpEntry =
+ sqlHistoricalOp.getDiscreteAccessAt(x);
+ AppOpsManager.AttributedOpEntry xmlOpEntry =
+ xmlHistoricalOp.getDiscreteAccessAt(x);
+ Slog.i(LOG_TAG, "sql keys: " + sqlOpEntry.collectKeys() + ", xml keys: "
+ + xmlOpEntry.collectKeys());
+ assertEquals(sqlOpEntry.collectKeys(), xmlOpEntry.collectKeys());
+ assertEquals(sqlOpEntry.isRunning(), xmlOpEntry.isRunning());
+ ArraySet<Long> keys = sqlOpEntry.collectKeys();
+ final int keyCount = keys.size();
+ for (int k = 0; k < keyCount; k++) {
+ final long key = keys.valueAt(k);
+ final int flags = extractFlagsFromKey(key);
+ assertEquals(sqlOpEntry.getLastDuration(flags),
+ xmlOpEntry.getLastDuration(flags));
+ assertEquals(sqlOpEntry.getLastProxyInfo(flags),
+ xmlOpEntry.getLastProxyInfo(flags));
+ assertEquals(sqlOpEntry.getLastAccessTime(flags),
+ xmlOpEntry.getLastAccessTime(flags));
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // code duplicated for assertions
+ private static final int FLAGS_MASK = 0xFFFFFFFF;
+
+ public static int extractFlagsFromKey(@AppOpsManager.DataBucketKey long key) {
+ return (int) (key & FLAGS_MASK);
+ }
+
+ private void assertEquals(Object actual, Object expected) {
+ if (!Objects.equals(actual, expected)) {
+ throw new IllegalStateException("Actual (" + actual + ") is not equal to expected ("
+ + expected + ")");
+ }
+ }
+
+ @Override
+ void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter,
+ @Nullable String attributionTagFilter, int filter, int dumpOp,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix,
+ int nDiscreteOps) {
+ mXmlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp,
+ sdf, date, prefix, nDiscreteOps);
+ pw.println("--------------------------------------------------------");
+ pw.println("--------------------------------------------------------");
+ mSqlRegistry.dump(pw, uidFilter, packageNameFilter, attributionTagFilter, filter, dumpOp,
+ sdf, date, prefix, nDiscreteOps);
+ }
+}
diff --git a/services/core/java/com/android/server/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
index 7f161f618618..a6e3fc7cc66a 100644
--- a/services/core/java/com/android/server/appop/DiscreteRegistry.java
+++ b/services/core/java/com/android/server/appop/DiscreteOpsXmlRegistry.java
@@ -24,48 +24,20 @@ import static android.app.AppOpsManager.FILTER_BY_ATTRIBUTION_TAG;
import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
import static android.app.AppOpsManager.FILTER_BY_UID;
-import static android.app.AppOpsManager.OP_CAMERA;
-import static android.app.AppOpsManager.OP_COARSE_LOCATION;
-import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION;
-import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static android.app.AppOpsManager.OP_FLAGS_ALL;
-import static android.app.AppOpsManager.OP_FLAG_SELF;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
-import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY;
-import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION;
-import static android.app.AppOpsManager.OP_MONITOR_LOCATION;
import static android.app.AppOpsManager.OP_NONE;
-import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA;
-import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE;
-import static android.app.AppOpsManager.OP_PROCESS_OUTGOING_CALLS;
-import static android.app.AppOpsManager.OP_READ_ICC_SMS;
-import static android.app.AppOpsManager.OP_READ_SMS;
-import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING;
-import static android.app.AppOpsManager.OP_SEND_SMS;
-import static android.app.AppOpsManager.OP_SMS_FINANCIAL_TRANSACTIONS;
-import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
-import static android.app.AppOpsManager.OP_WRITE_ICC_SMS;
-import static android.app.AppOpsManager.OP_WRITE_SMS;
import static android.app.AppOpsManager.flagsToString;
import static android.app.AppOpsManager.getUidStateName;
import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
-import static java.lang.Long.min;
import static java.lang.Math.max;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
-import android.os.AsyncTask;
-import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.permission.flags.Flags;
-import android.provider.DeviceConfig;
import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
@@ -84,10 +56,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
-import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
@@ -99,100 +68,30 @@ import java.util.Objects;
import java.util.Set;
/**
- * This class manages information about recent accesses to ops for permission usage timeline.
- *
- * The discrete history is kept for limited time (initial default is 24 hours, set in
- * {@link DiscreteRegistry#sDiscreteHistoryCutoff) and discarded after that.
- *
- * Discrete history is quantized to reduce resources footprint. By default quantization is set to
- * one minute in {@link DiscreteRegistry#sDiscreteHistoryQuantization}. All access times are aligned
- * to the closest quantized time. All durations (except -1, meaning no duration) are rounded up to
- * the closest quantized interval.
- *
- * When data is queried through API, events are deduplicated and for every time quant there can
- * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about
- * different accesses which happened in specified time quant - across dimensions of
- * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension
- * it is only possible to know if at least one access happened in the time quant.
+ * Xml persistence implementation for discrete ops.
*
+ * <p>
* Every time state is saved (default is 30 minutes), memory state is dumped to a
* new file and memory state is cleared. Files older than time limit are deleted
* during the process.
- *
+ * <p>
* When request comes in, files are read and requested information is collected
* and delivered. Information is cached in memory until the next state save (up to 30 minutes), to
* avoid reading disk if more API calls come in a quick succession.
- *
+ * <p>
* THREADING AND LOCKING:
- * For in-memory transactions this class relies on {@link DiscreteRegistry#mInMemoryLock}. It is
- * assumed that the same lock is used for in-memory transactions in {@link AppOpsService},
- * {@link HistoricalRegistry}, and {@link DiscreteRegistry}.
- * {@link DiscreteRegistry#recordDiscreteAccess(int, String, int, String, int, int, long, long)}
- * must only be called while holding this lock.
- * {@link DiscreteRegistry#mOnDiskLock} is used when disk transactions are performed.
- * It is very important to release {@link DiscreteRegistry#mInMemoryLock} as soon as possible, as
- * no AppOps related transactions across the system can be performed while it is held.
+ * For in-memory transactions this class relies on {@link DiscreteOpsXmlRegistry#mInMemoryLock}.
+ * It is assumed that the same lock is used for in-memory transactions in {@link AppOpsService},
+ * {@link HistoricalRegistry}, and {@link DiscreteOpsXmlRegistry }.
+ * {@link DiscreteOpsRegistry#recordDiscreteAccess} must only be called while holding this lock.
+ * {@link DiscreteOpsXmlRegistry#mOnDiskLock} is used when disk transactions are performed.
+ * It is very important to release {@link DiscreteOpsXmlRegistry#mInMemoryLock} as soon as
+ * possible, as no AppOps related transactions across the system can be performed while it is held.
*
- * INITIALIZATION: We can initialize persistence only after the system is ready
- * as we need to check the optional configuration override from the settings
- * database which is not initialized at the time the app ops service is created. This class
- * relies on {@link HistoricalRegistry} for controlling that no calls are allowed until then. All
- * outside calls are going through {@link HistoricalRegistry}, where
- * {@link HistoricalRegistry#isPersistenceInitializedMLocked()} check is done.
*/
-
-final class DiscreteRegistry {
+class DiscreteOpsXmlRegistry extends DiscreteOpsRegistry {
static final String DISCRETE_HISTORY_FILE_SUFFIX = "tl";
- private static final String TAG = DiscreteRegistry.class.getSimpleName();
-
- private static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis";
- private static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION =
- "discrete_history_quantization_millis";
- private static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags";
- private static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist";
- private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION
- + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + ","
- + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + ","
- + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
- + "," + OP_RESERVED_FOR_TESTING;
- private static final int[] sDiscreteOpsToLog =
- new int[]{OP_FINE_LOCATION, OP_COARSE_LOCATION, OP_EMERGENCY_LOCATION, OP_CAMERA,
- OP_RECORD_AUDIO, OP_PHONE_CALL_MICROPHONE, OP_PHONE_CALL_CAMERA,
- OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, OP_READ_SMS,
- OP_WRITE_SMS, OP_SEND_SMS, OP_READ_ICC_SMS, OP_WRITE_ICC_SMS,
- OP_SMS_FINANCIAL_TRANSACTIONS, OP_SYSTEM_ALERT_WINDOW, OP_MONITOR_LOCATION,
- OP_MONITOR_HIGH_POWER_LOCATION, OP_PROCESS_OUTGOING_CALLS,
- };
- private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis();
- private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis();
- private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION =
- Duration.ofMinutes(1).toMillis();
-
- static final int ACCESS_TYPE_NOTE_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__NOTE_OP;
- static final int ACCESS_TYPE_START_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__START_OP;
- static final int ACCESS_TYPE_FINISH_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__FINISH_OP;
- static final int ACCESS_TYPE_PAUSE_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__PAUSE_OP;
- static final int ACCESS_TYPE_RESUME_OP =
- FrameworkStatsLog.APP_OP_ACCESS_TRACKED__ACCESS_TYPE__RESUME_OP;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"ACCESS_TYPE_"}, value = {
- ACCESS_TYPE_NOTE_OP,
- ACCESS_TYPE_START_OP,
- ACCESS_TYPE_FINISH_OP,
- ACCESS_TYPE_PAUSE_OP,
- ACCESS_TYPE_RESUME_OP
- })
- public @interface AccessType {}
-
- private static long sDiscreteHistoryCutoff;
- private static long sDiscreteHistoryQuantization;
- private static int[] sDiscreteOps;
- private static int sDiscreteFlags;
+ private static final String TAG = DiscreteOpsXmlRegistry.class.getSimpleName();
private static final String TAG_HISTORY = "h";
private static final String ATTR_VERSION = "v";
@@ -221,9 +120,6 @@ final class DiscreteRegistry {
private static final String ATTR_ATTRIBUTION_FLAGS = "af";
private static final String ATTR_CHAIN_ID = "ci";
- private static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED
- | OP_FLAG_TRUSTED_PROXY;
-
// Lock for read/write access to on disk state
private final Object mOnDiskLock = new Object();
@@ -239,14 +135,12 @@ final class DiscreteRegistry {
@GuardedBy("mOnDiskLock")
private DiscreteOps mCachedOps = null;
- private boolean mDebugMode = false;
-
- DiscreteRegistry(Object inMemoryLock) {
- this(inMemoryLock, new File(new File(Environment.getDataSystemDirectory(), "appops"),
- "discrete"));
+ DiscreteOpsXmlRegistry(Object inMemoryLock) {
+ this(inMemoryLock, getDiscreteOpsDir());
}
- DiscreteRegistry(Object inMemoryLock, File discreteAccessDir) {
+ // constructor for tests.
+ DiscreteOpsXmlRegistry(Object inMemoryLock, File discreteAccessDir) {
mInMemoryLock = inMemoryLock;
synchronized (mOnDiskLock) {
mDiscreteAccessDir = discreteAccessDir;
@@ -258,40 +152,8 @@ final class DiscreteRegistry {
}
}
- void systemReady() {
- DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
- AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> {
- setDiscreteHistoryParameters(p);
- });
- setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY));
- }
-
- private void setDiscreteHistoryParameters(DeviceConfig.Properties p) {
- if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) {
- sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF,
- DEFAULT_DISCRETE_HISTORY_CUTOFF);
- if (!Build.IS_DEBUGGABLE && !mDebugMode) {
- sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF,
- sDiscreteHistoryCutoff);
- }
- } else {
- sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF;
- }
- if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) {
- sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION,
- DEFAULT_DISCRETE_HISTORY_QUANTIZATION);
- if (!Build.IS_DEBUGGABLE && !mDebugMode) {
- sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION,
- sDiscreteHistoryQuantization);
- }
- } else {
- sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION;
- }
- sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) ? sDiscreteFlags =
- p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE;
- sDiscreteOps = p.getKeyset().contains(PROPERTY_DISCRETE_OPS_LIST) ? parseOpsList(
- p.getString(PROPERTY_DISCRETE_OPS_LIST, DEFAULT_DISCRETE_OPS)) : parseOpsList(
- DEFAULT_DISCRETE_OPS);
+ static File getDiscreteOpsDir() {
+ return new File(new File(Environment.getDataSystemDirectory(), "appops"), "discrete");
}
void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op,
@@ -300,17 +162,9 @@ final class DiscreteRegistry {
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
@AccessType int accessType) {
if (shouldLogAccess(op)) {
- int firstChar = 0;
- if (attributionTag != null && attributionTag.startsWith(packageName)) {
- firstChar = packageName.length();
- if (firstChar < attributionTag.length() && attributionTag.charAt(firstChar)
- == '.') {
- firstChar++;
- }
- }
FrameworkStatsLog.write(FrameworkStatsLog.APP_OP_ACCESS_TRACKED, uid, op, accessType,
uidState, flags, attributionFlags,
- attributionTag == null ? null : attributionTag.substring(firstChar),
+ getAttributionTag(attributionTag, packageName),
attributionChainId);
}
@@ -331,7 +185,7 @@ final class DiscreteRegistry {
}
}
- void writeAndClearAccessHistory() {
+ void writeAndClearOldAccessHistory() {
synchronized (mOnDiskLock) {
if (mDiscreteAccessDir == null) {
Slog.d(TAG, "State not saved - persistence not initialized.");
@@ -350,6 +204,22 @@ final class DiscreteRegistry {
}
}
+ void migrateSqliteData(DiscreteOps sqliteOps) {
+ synchronized (mOnDiskLock) {
+ if (mDiscreteAccessDir == null) {
+ Slog.d(TAG, "State not saved - persistence not initialized.");
+ return;
+ }
+ synchronized (mInMemoryLock) {
+ mDiscreteOps.mLargestChainId = sqliteOps.mLargestChainId;
+ mDiscreteOps.mChainIdOffset = sqliteOps.mChainIdOffset;
+ }
+ if (!sqliteOps.isEmpty()) {
+ persistDiscreteOpsLocked(sqliteOps);
+ }
+ }
+ }
+
void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result,
long beginTimeMillis, long endTimeMillis,
@AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter,
@@ -369,7 +239,7 @@ final class DiscreteRegistry {
discreteOps.applyToHistoricalOps(result, attributionChains);
}
- private int readLargestChainIdFromDiskLocked() {
+ int readLargestChainIdFromDiskLocked() {
final File[] files = mDiscreteAccessDir.listFiles();
if (files != null && files.length > 0) {
File latestFile = null;
@@ -497,6 +367,13 @@ final class DiscreteRegistry {
}
}
+ void deleteDiscreteOpsDir() {
+ synchronized (mOnDiskLock) {
+ mCachedOps = null;
+ FileUtils.deleteContentsAndDir(mDiscreteAccessDir);
+ }
+ }
+
void clearHistory(int uid, String packageName) {
synchronized (mOnDiskLock) {
DiscreteOps discreteOps;
@@ -1506,26 +1383,6 @@ final class DiscreteRegistry {
}
}
- private static int[] parseOpsList(String opsList) {
- String[] strArr;
- if (opsList.isEmpty()) {
- strArr = new String[0];
- } else {
- strArr = opsList.split(",");
- }
- int nOps = strArr.length;
- int[] result = new int[nOps];
- try {
- for (int i = 0; i < nOps; i++) {
- result[i] = Integer.parseInt(strArr[i]);
- }
- } catch (NumberFormatException e) {
- Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage());
- return parseOpsList(DEFAULT_DISCRETE_OPS);
- }
- return result;
- }
-
private static List<DiscreteOpEvent> stableListMerge(List<DiscreteOpEvent> a,
List<DiscreteOpEvent> b) {
int nA = a.size();
@@ -1570,34 +1427,4 @@ final class DiscreteRegistry {
}
return result;
}
-
- private static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) {
- if (!ArrayUtils.contains(sDiscreteOps, op)) {
- return false;
- }
- if ((flags & (sDiscreteFlags)) == 0) {
- return false;
- }
- return true;
- }
-
- private static boolean shouldLogAccess(int op) {
- return Flags.appopAccessTrackingLoggingEnabled()
- && ArrayUtils.contains(sDiscreteOpsToLog, op);
- }
-
- private static long discretizeTimeStamp(long timeStamp) {
- return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
-
- }
-
- private static long discretizeDuration(long duration) {
- return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1)
- / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization;
- }
-
- void setDebugMode(boolean debugMode) {
- this.mDebugMode = debugMode;
- }
}
-
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 5e67f26ba1f6..ba391d0a9995 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -35,6 +35,7 @@ import android.app.AppOpsManager.OpFlags;
import android.app.AppOpsManager.OpHistoryFlags;
import android.app.AppOpsManager.UidState;
import android.content.ContentResolver;
+import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Build;
@@ -45,6 +46,7 @@ import android.os.Message;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.UserHandle;
+import android.permission.flags.Flags;
import android.provider.Settings;
import android.util.ArraySet;
import android.util.LongSparseArray;
@@ -135,7 +137,7 @@ final class HistoricalRegistry {
private static final String PARAMETER_DELIMITER = ",";
private static final String PARAMETER_ASSIGNMENT = "=";
- private volatile @NonNull DiscreteRegistry mDiscreteRegistry;
+ private volatile @NonNull DiscreteOpsRegistry mDiscreteRegistry;
@GuardedBy("mLock")
private @NonNull LinkedList<HistoricalOps> mPendingWrites = new LinkedList<>();
@@ -196,13 +198,30 @@ final class HistoricalRegistry {
@GuardedBy("mOnDiskLock")
private Persistence mPersistence;
- HistoricalRegistry(@NonNull Object lock) {
+ private final Context mContext;
+
+ HistoricalRegistry(@NonNull Object lock, Context context) {
mInMemoryLock = lock;
- mDiscreteRegistry = new DiscreteRegistry(lock);
+ mContext = context;
+ if (Flags.enableSqliteAppopsAccesses()) {
+ mDiscreteRegistry = new DiscreteOpsSqlRegistry(context);
+ if (DiscreteOpsXmlRegistry.getDiscreteOpsDir().exists()) {
+ DiscreteOpsSqlRegistry sqlRegistry = (DiscreteOpsSqlRegistry) mDiscreteRegistry;
+ DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(context);
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+ }
+ } else {
+ mDiscreteRegistry = new DiscreteOpsXmlRegistry(context);
+ if (DiscreteOpsDbHelper.getDatabaseFile().exists()) { // roll-back sqlite
+ DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(context);
+ DiscreteOpsXmlRegistry xmlRegistry = (DiscreteOpsXmlRegistry) mDiscreteRegistry;
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+ }
+ }
}
HistoricalRegistry(@NonNull HistoricalRegistry other) {
- this(other.mInMemoryLock);
+ this(other.mInMemoryLock, other.mContext);
mMode = other.mMode;
mBaseSnapshotInterval = other.mBaseSnapshotInterval;
mIntervalCompressionMultiplier = other.mIntervalCompressionMultiplier;
@@ -475,7 +494,7 @@ final class HistoricalRegistry {
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long accessTime,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteRegistry.AccessType int accessType, int accessCount) {
+ @DiscreteOpsRegistry.AccessType int accessType, int accessCount) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -512,7 +531,7 @@ final class HistoricalRegistry {
@NonNull String deviceId, @Nullable String attributionTag, @UidState int uidState,
@OpFlags int flags, long eventStartTime, long increment,
@AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId,
- @DiscreteRegistry.AccessType int accessType) {
+ @DiscreteOpsRegistry.AccessType int accessType) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
@@ -648,7 +667,7 @@ final class HistoricalRegistry {
}
void writeAndClearDiscreteHistory() {
- mDiscreteRegistry.writeAndClearAccessHistory();
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
}
void clearAllHistory() {
@@ -743,7 +762,7 @@ final class HistoricalRegistry {
}
persistPendingHistory(pendingWrites);
}
- mDiscreteRegistry.writeAndClearAccessHistory();
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
}
private void persistPendingHistory(@NonNull List<HistoricalOps> pendingWrites) {
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 5d9db65fe2b2..d89db8d5581b 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -312,6 +312,13 @@ public final class DeviceStateManagerService extends SystemService {
mProcessObserver);
}
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ mDeviceStatePolicy.getDeviceStateProvider().onSystemReady();
+ }
+ }
+
@VisibleForTesting
Handler getHandler() {
return mHandler;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
index 8d07609cef30..8a8ebc2ffc21 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateProvider.java
@@ -91,6 +91,11 @@ public interface DeviceStateProvider extends Dumpable {
@interface SupportedStatesUpdatedReason {}
/**
+ * Called when the system boot phase advances to PHASE_SYSTEM_SERVICES_READY.
+ */
+ default void onSystemReady() {};
+
+ /**
* Registers a listener for changes in provider state.
* <p>
* It is <b>required</b> that
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index 9f785ac81398..24296406da00 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -19,6 +19,7 @@ package com.android.server.input;
import static android.hardware.input.InputGestureData.createKeyTrigger;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
@@ -240,6 +241,13 @@ final class InputGestureManager {
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_ACTIVATE_SELECT_TO_SPEAK));
}
+ if (enableVoiceAccessKeyGestures()) {
+ systemShortcuts.add(
+ createKeyGesture(
+ KeyEvent.KEYCODE_V,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ }
if (enableTaskResizingKeyboardShortcuts()) {
systemShortcuts.add(createKeyGesture(
KeyEvent.KEYCODE_LEFT_BRACKET,
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index bc44fed21f2d..4e5c720f9f1c 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -104,13 +104,16 @@ public abstract class InputManagerInternal {
public abstract PointF getCursorPosition(int displayId);
/**
- * Enables or disables pointer acceleration for mouse movements.
+ * Set whether all pointer scaling, including linear scaling based on the
+ * user's pointer speed setting, should be enabled or disabled for mice.
*
* Note that this only affects pointer movements from mice (that is, pointing devices which send
* relative motions, including trackballs and pointing sticks), not from other pointer devices
* such as touchpads and styluses.
+ *
+ * Scaling is enabled by default on new displays until it is explicitly disabled.
*/
- public abstract void setMousePointerAccelerationEnabled(boolean enabled, int displayId);
+ public abstract void setMouseScalingEnabled(boolean enabled, int displayId);
/**
* Sets the eligibility of windows on a given display for pointer capture. If a display is
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 559b4ae64e50..b2c35e1f362e 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1382,9 +1382,9 @@ public class InputManagerService extends IInputManager.Stub
mNative.setPointerSpeed(speed);
}
- private void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
+ private void setMouseScalingEnabled(boolean enabled, int displayId) {
updateAdditionalDisplayInputProperties(displayId,
- properties -> properties.mousePointerAccelerationEnabled = enabled);
+ properties -> properties.mouseScalingEnabled = enabled);
}
private void setPointerIconVisible(boolean visible, int displayId) {
@@ -2232,8 +2232,8 @@ public class InputManagerService extends IInputManager.Stub
pw.println("displayId: " + mAdditionalDisplayInputProperties.keyAt(i));
final AdditionalDisplayInputProperties properties =
mAdditionalDisplayInputProperties.valueAt(i);
- pw.println("mousePointerAccelerationEnabled: "
- + properties.mousePointerAccelerationEnabled);
+ pw.println("mouseScalingEnabled: "
+ + properties.mouseScalingEnabled);
pw.println("pointerIconVisible: " + properties.pointerIconVisible);
}
} finally {
@@ -3575,8 +3575,8 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
- public void setMousePointerAccelerationEnabled(boolean enabled, int displayId) {
- InputManagerService.this.setMousePointerAccelerationEnabled(enabled, displayId);
+ public void setMouseScalingEnabled(boolean enabled, int displayId) {
+ InputManagerService.this.setMouseScalingEnabled(enabled, displayId);
}
@Override
@@ -3716,15 +3716,15 @@ public class InputManagerService extends IInputManager.Stub
private static class AdditionalDisplayInputProperties {
static final boolean DEFAULT_POINTER_ICON_VISIBLE = true;
- static final boolean DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED = true;
+ static final boolean DEFAULT_MOUSE_SCALING_ENABLED = true;
/**
- * Whether to enable mouse pointer acceleration on this display. Note that this only affects
+ * Whether to enable mouse pointer scaling on this display. Note that this only affects
* pointer movements from mice (that is, pointing devices which send relative motions,
* including trackballs and pointing sticks), not from other pointer devices such as
* touchpads and styluses.
*/
- public boolean mousePointerAccelerationEnabled;
+ public boolean mouseScalingEnabled;
// Whether the pointer icon should be visible or hidden on this display.
public boolean pointerIconVisible;
@@ -3734,12 +3734,12 @@ public class InputManagerService extends IInputManager.Stub
}
public boolean allDefaults() {
- return mousePointerAccelerationEnabled == DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED
+ return mouseScalingEnabled == DEFAULT_MOUSE_SCALING_ENABLED
&& pointerIconVisible == DEFAULT_POINTER_ICON_VISIBLE;
}
public void reset() {
- mousePointerAccelerationEnabled = DEFAULT_MOUSE_POINTER_ACCELERATION_ENABLED;
+ mouseScalingEnabled = DEFAULT_MOUSE_SCALING_ENABLED;
pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE;
}
}
@@ -3754,14 +3754,14 @@ public class InputManagerService extends IInputManager.Stub
mAdditionalDisplayInputProperties.put(displayId, properties);
}
final boolean oldPointerIconVisible = properties.pointerIconVisible;
- final boolean oldMouseAccelerationEnabled = properties.mousePointerAccelerationEnabled;
+ final boolean oldMouseScalingEnabled = properties.mouseScalingEnabled;
updater.accept(properties);
if (oldPointerIconVisible != properties.pointerIconVisible) {
mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
}
- if (oldMouseAccelerationEnabled != properties.mousePointerAccelerationEnabled) {
- mNative.setMousePointerAccelerationEnabled(displayId,
- properties.mousePointerAccelerationEnabled);
+ if (oldMouseScalingEnabled != properties.mouseScalingEnabled) {
+ mNative.setMouseScalingEnabled(displayId,
+ properties.mouseScalingEnabled);
}
if (properties.allDefaults()) {
mAdditionalDisplayInputProperties.remove(displayId);
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index febf24edc294..e25ea4b43827 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -74,6 +74,8 @@ class InputSettingsObserver extends ContentObserver {
Map.entry(Settings.System.getUriFor(
Settings.System.MOUSE_POINTER_ACCELERATION_ENABLED),
(reason) -> updateMouseAccelerationEnabled()),
+ Map.entry(Settings.System.getUriFor(Settings.System.MOUSE_SCROLLING_SPEED),
+ (reason) -> updateMouseScrollingSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_POINTER_SPEED),
(reason) -> updateTouchpadPointerSpeed()),
Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_NATURAL_SCROLLING),
@@ -199,6 +201,11 @@ class InputSettingsObserver extends ContentObserver {
InputSettings.isMousePointerAccelerationEnabled(mContext));
}
+ private void updateMouseScrollingSpeed() {
+ mNative.setMouseScrollingSpeed(
+ constrainPointerSpeedValue(InputSettings.getMouseScrollingSpeed(mContext)));
+ }
+
private void updateTouchpadPointerSpeed() {
mNative.setTouchpadPointerSpeed(
constrainPointerSpeedValue(InputSettings.getTouchpadPointerSpeed(mContext)));
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 7dbde64a6412..4d38c8401e2d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -130,12 +130,14 @@ interface NativeInputManagerService {
void setPointerSpeed(int speed);
- void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ void setMouseScalingEnabled(int displayId, boolean enabled);
void setMouseReverseVerticalScrollingEnabled(boolean enabled);
void setMouseScrollingAccelerationEnabled(boolean enabled);
+ void setMouseScrollingSpeed(int speed);
+
void setMouseSwapPrimaryButtonEnabled(boolean enabled);
void setMouseAccelerationEnabled(boolean enabled);
@@ -419,7 +421,7 @@ interface NativeInputManagerService {
public native void setPointerSpeed(int speed);
@Override
- public native void setMousePointerAccelerationEnabled(int displayId, boolean enabled);
+ public native void setMouseScalingEnabled(int displayId, boolean enabled);
@Override
public native void setMouseReverseVerticalScrollingEnabled(boolean enabled);
@@ -428,6 +430,9 @@ interface NativeInputManagerService {
public native void setMouseScrollingAccelerationEnabled(boolean enabled);
@Override
+ public native void setMouseScrollingSpeed(int speed);
+
+ @Override
public native void setMouseSwapPrimaryButtonEnabled(boolean enabled);
@Override
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 34bb4155c943..86fc732e9d04 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -18,6 +18,7 @@ package com.android.server.media.quality;
import android.content.ContentValues;
import android.content.Context;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.media.quality.AmbientBacklightSettings;
@@ -35,8 +36,13 @@ import android.media.quality.SoundProfileHandle;
import android.os.Binder;
import android.os.Bundle;
import android.os.PersistableBundle;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
+import android.util.Slog;
+import android.util.SparseArray;
import com.android.server.SystemService;
@@ -45,9 +51,11 @@ import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@@ -64,10 +72,13 @@ public class MediaQualityService extends SystemService {
private final MediaQualityDbHelper mMediaQualityDbHelper;
private final BiMap<Long, String> mPictureProfileTempIdMap;
private final BiMap<Long, String> mSoundProfileTempIdMap;
+ private final PackageManager mPackageManager;
+ private final SparseArray<UserState> mUserStates = new SparseArray<>();
public MediaQualityService(Context context) {
super(context);
mContext = context;
+ mPackageManager = mContext.getPackageManager();
mPictureProfileTempIdMap = new BiMap<>();
mSoundProfileTempIdMap = new BiMap<>();
mMediaQualityDbHelper = new MediaQualityDbHelper(mContext);
@@ -85,12 +96,20 @@ public class MediaQualityService extends SystemService {
@Override
public PictureProfile createPictureProfile(PictureProfile pp, UserHandle user) {
+ if ((pp.getPackageName() != null && !pp.getPackageName().isEmpty()
+ && !incomingPackageEqualsCallingUidPackage(pp.getPackageName()))
+ && !hasGlobalPictureQualityServicePermission()) {
+ notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
ContentValues values = getContentValues(null,
pp.getProfileType(),
pp.getName(),
- pp.getPackageName(),
+ pp.getPackageName() == null || pp.getPackageName().isEmpty()
+ ? getPackageOfCallingUid() : pp.getPackageName(),
pp.getInputId(),
pp.getParameters());
@@ -104,9 +123,13 @@ public class MediaQualityService extends SystemService {
@Override
public void updatePictureProfile(String id, PictureProfile pp, UserHandle user) {
- Long intId = mPictureProfileTempIdMap.getKey(id);
+ Long dbId = mPictureProfileTempIdMap.getKey(id);
+ if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
+ notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
- ContentValues values = getContentValues(intId,
+ ContentValues values = getContentValues(dbId,
pp.getProfileType(),
pp.getName(),
pp.getPackageName(),
@@ -118,27 +141,51 @@ public class MediaQualityService extends SystemService {
null, values);
}
+ private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
+ PictureProfile fromDb = getPictureProfile(dbId);
+ return fromDb.getProfileType() == toUpdate.getProfileType()
+ && fromDb.getPackageName().equals(toUpdate.getPackageName())
+ && fromDb.getName().equals(toUpdate.getName())
+ && fromDb.getName().equals(getPackageOfCallingUid());
+ }
+
@Override
public void removePictureProfile(String id, UserHandle user) {
- Long intId = mPictureProfileTempIdMap.getKey(id);
- if (intId != null) {
+ Long dbId = mPictureProfileTempIdMap.getKey(id);
+
+ if (!hasPermissionToRemovePictureProfile(dbId)) {
+ notifyError(id, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
+ if (dbId != null) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
String selection = BaseParameters.PARAMETER_ID + " = ?";
- String[] selectionArgs = {Long.toString(intId)};
- db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
+ String[] selectionArgs = {Long.toString(dbId)};
+ int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, selection,
selectionArgs);
- mPictureProfileTempIdMap.remove(intId);
+ if (result == 0) {
+ notifyError(id, PictureProfile.ERROR_INVALID_ARGUMENT,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ mPictureProfileTempIdMap.remove(dbId);
}
}
+ private boolean hasPermissionToRemovePictureProfile(Long dbId) {
+ PictureProfile fromDb = getPictureProfile(dbId);
+ return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ }
+
@Override
public PictureProfile getPictureProfile(int type, String name, Bundle options,
UserHandle user) {
boolean includeParams =
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
- + BaseParameters.PARAMETER_NAME + " = ?";
- String[] selectionArguments = {Integer.toString(type), name};
+ + BaseParameters.PARAMETER_NAME + " = ? AND "
+ + BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {Integer.toString(type), name, getPackageOfCallingUid()};
try (
Cursor cursor = getCursorAfterQuerying(
@@ -156,13 +203,42 @@ public class MediaQualityService extends SystemService {
return null;
}
cursor.moveToFirst();
- return getPictureProfileWithTempIdFromCursor(cursor);
+ return convertCursorToPictureProfileWithTempId(cursor);
+ }
+ }
+
+ private PictureProfile getPictureProfile(Long dbId) {
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArguments = {Long.toString(dbId)};
+
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ getMediaProfileColumns(false), selection, selectionArguments)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%d"
+ + " in %s. Should only ever be 0 or 1.", count, dbId,
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME));
+ return null;
+ }
+ cursor.moveToFirst();
+ return convertCursorToPictureProfileWithTempId(cursor);
}
}
@Override
public List<PictureProfile> getPictureProfilesByPackage(
String packageName, Bundle options, UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+
boolean includeParams =
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -172,23 +248,31 @@ public class MediaQualityService extends SystemService {
}
@Override
- public List<PictureProfile> getAvailablePictureProfiles(Bundle options, UserHandle user) {
- String[] packageNames = mContext.getPackageManager().getPackagesForUid(
- Binder.getCallingUid());
- if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
- return getPictureProfilesByPackage(packageNames[0], options, user);
+ public List<PictureProfile> getAvailablePictureProfiles(
+ Bundle options, UserHandle user) {
+ String packageName = getPackageOfCallingUid();
+ if (packageName != null) {
+ return getPictureProfilesByPackage(packageName, options, user);
}
return new ArrayList<>();
}
@Override
public boolean setDefaultPictureProfile(String profileId, UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ notifyError(profileId, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
// TODO: pass the profile ID to MediaQuality HAL when ready.
return false;
}
@Override
public List<String> getPictureProfilePackageNames(UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ notifyError(null, PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
String [] column = {BaseParameters.PARAMETER_PACKAGE};
List<PictureProfile> pictureProfiles = getPictureProfilesBasedOnConditions(column,
null, null);
@@ -210,12 +294,19 @@ public class MediaQualityService extends SystemService {
@Override
public SoundProfile createSoundProfile(SoundProfile sp, UserHandle user) {
+ if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
+ && !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
+ && !hasGlobalPictureQualityServicePermission()) {
+ //TODO: error handling
+ return null;
+ }
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
ContentValues values = getContentValues(null,
sp.getProfileType(),
sp.getName(),
- sp.getPackageName(),
+ sp.getPackageName() == null || sp.getPackageName().isEmpty()
+ ? getPackageOfCallingUid() : sp.getPackageName(),
sp.getInputId(),
sp.getParameters());
@@ -229,9 +320,14 @@ public class MediaQualityService extends SystemService {
@Override
public void updateSoundProfile(String id, SoundProfile sp, UserHandle user) {
- Long intId = mSoundProfileTempIdMap.getKey(id);
+ Long dbId = mSoundProfileTempIdMap.getKey(id);
+
+ if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
+ //TODO: error handling
+ return;
+ }
- ContentValues values = getContentValues(intId,
+ ContentValues values = getContentValues(dbId,
sp.getProfileType(),
sp.getName(),
sp.getPackageName(),
@@ -242,27 +338,49 @@ public class MediaQualityService extends SystemService {
db.replace(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, null, values);
}
+ private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
+ SoundProfile fromDb = getSoundProfile(dbId);
+ return fromDb.getProfileType() == sp.getProfileType()
+ && fromDb.getPackageName().equals(sp.getPackageName())
+ && fromDb.getName().equals(sp.getName())
+ && fromDb.getName().equals(getPackageOfCallingUid());
+ }
+
@Override
public void removeSoundProfile(String id, UserHandle user) {
Long intId = mSoundProfileTempIdMap.getKey(id);
+ if (!hasPermissionToRemoveSoundProfile(intId)) {
+ //TODO: error handling
+ return;
+ }
+
if (intId != null) {
SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
String selection = BaseParameters.PARAMETER_ID + " = ?";
String[] selectionArgs = {Long.toString(intId)};
- db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
+ int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, selection,
selectionArgs);
+ if (result == 0) {
+ //TODO: error handling
+ }
mSoundProfileTempIdMap.remove(intId);
}
}
+ private boolean hasPermissionToRemoveSoundProfile(Long dbId) {
+ SoundProfile fromDb = getSoundProfile(dbId);
+ return fromDb.getName().equalsIgnoreCase(getPackageOfCallingUid());
+ }
+
@Override
- public SoundProfile getSoundProfile(int type, String id, Bundle options,
+ public SoundProfile getSoundProfile(int type, String name, Bundle options,
UserHandle user) {
boolean includeParams =
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
- + BaseParameters.PARAMETER_ID + " = ?";
- String[] selectionArguments = {String.valueOf(type), id};
+ + BaseParameters.PARAMETER_NAME + " = ? AND "
+ + BaseParameters.PARAMETER_PACKAGE + " = ?";
+ String[] selectionArguments = {String.valueOf(type), name, getPackageOfCallingUid()};
try (
Cursor cursor = getCursorAfterQuerying(
@@ -275,18 +393,47 @@ public class MediaQualityService extends SystemService {
}
if (count > 1) {
Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s"
- + " in %s. Should only ever be 0 or 1.", count, id,
+ + " in %s. Should only ever be 0 or 1.", count, name,
mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
return null;
}
cursor.moveToFirst();
- return getSoundProfileWithTempIdFromCursor(cursor);
+ return convertCursorToSoundProfileWithTempId(cursor);
+ }
+ }
+
+ private SoundProfile getSoundProfile(Long dbId) {
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArguments = {Long.toString(dbId)};
+
+ try (
+ Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ getMediaProfileColumns(false), selection, selectionArguments)
+ ) {
+ int count = cursor.getCount();
+ if (count == 0) {
+ return null;
+ }
+ if (count > 1) {
+ Log.wtf(TAG, String.format(Locale.US, "%d entries found for id=%s "
+ + "in %s. Should only ever be 0 or 1.", count, dbId,
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME));
+ return null;
+ }
+ cursor.moveToFirst();
+ return convertCursorToSoundProfileWithTempId(cursor);
}
}
@Override
public List<SoundProfile> getSoundProfilesByPackage(
String packageName, Bundle options, UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ return new ArrayList<>();
+ }
+
boolean includeParams =
options.getBoolean(MediaQualityManager.OPTION_INCLUDE_PARAMETERS, false);
String selection = BaseParameters.PARAMETER_PACKAGE + " = ?";
@@ -296,24 +443,30 @@ public class MediaQualityService extends SystemService {
}
@Override
- public List<SoundProfile> getAvailableSoundProfiles(
- Bundle options, UserHandle user) {
- String[] packageNames = mContext.getPackageManager().getPackagesForUid(
- Binder.getCallingUid());
- if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
- return getSoundProfilesByPackage(packageNames[0], options, user);
+ public List<SoundProfile> getAvailableSoundProfiles(Bundle options, UserHandle user) {
+ String packageName = getPackageOfCallingUid();
+ if (packageName != null) {
+ return getSoundProfilesByPackage(packageName, options, user);
}
return new ArrayList<>();
}
@Override
public boolean setDefaultSoundProfile(String profileId, UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ return false;
+ }
// TODO: pass the profile ID to MediaQuality HAL when ready.
return false;
}
@Override
public List<String> getSoundProfilePackageNames(UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ return new ArrayList<>();
+ }
String [] column = {BaseParameters.PARAMETER_NAME};
List<SoundProfile> soundProfiles = getSoundProfilesBasedOnConditions(column,
null, null);
@@ -323,6 +476,37 @@ public class MediaQualityService extends SystemService {
.collect(Collectors.toList());
}
+ private String getPackageOfCallingUid() {
+ String[] packageNames = mPackageManager.getPackagesForUid(
+ Binder.getCallingUid());
+ if (packageNames != null && packageNames.length == 1 && !packageNames[0].isEmpty()) {
+ return packageNames[0];
+ }
+ return null;
+ }
+
+ private boolean incomingPackageEqualsCallingUidPackage(String incomingPackage) {
+ return incomingPackage.equalsIgnoreCase(getPackageOfCallingUid());
+ }
+
+ private boolean hasGlobalPictureQualityServicePermission() {
+ return mPackageManager.checkPermission(android.Manifest.permission
+ .MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE,
+ mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean hasGlobalSoundQualityServicePermission() {
+ return mPackageManager.checkPermission(android.Manifest.permission
+ .MANAGE_GLOBAL_SOUND_QUALITY_SERVICE,
+ mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean hasReadColorZonesPermission() {
+ return mPackageManager.checkPermission(android.Manifest.permission
+ .READ_COLOR_ZONES,
+ mContext.getPackageName()) == mPackageManager.PERMISSION_GRANTED;
+ }
+
private void populateTempIdMap(BiMap<Long, String> map, Long id) {
if (id != null && map.getValue(id) == null) {
String uuid;
@@ -430,7 +614,7 @@ public class MediaQualityService extends SystemService {
return columns.toArray(new String[0]);
}
- private PictureProfile getPictureProfileWithTempIdFromCursor(Cursor cursor) {
+ private PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor) {
return new PictureProfile(
getTempId(mPictureProfileTempIdMap, cursor),
getType(cursor),
@@ -442,7 +626,7 @@ public class MediaQualityService extends SystemService {
);
}
- private SoundProfile getSoundProfileWithTempIdFromCursor(Cursor cursor) {
+ private SoundProfile convertCursorToSoundProfileWithTempId(Cursor cursor) {
return new SoundProfile(
getTempId(mSoundProfileTempIdMap, cursor),
getType(cursor),
@@ -502,7 +686,7 @@ public class MediaQualityService extends SystemService {
) {
List<PictureProfile> pictureProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
- pictureProfiles.add(getPictureProfileWithTempIdFromCursor(cursor));
+ pictureProfiles.add(convertCursorToPictureProfileWithTempId(cursor));
}
return pictureProfiles;
}
@@ -517,30 +701,64 @@ public class MediaQualityService extends SystemService {
) {
List<SoundProfile> soundProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
- soundProfiles.add(getSoundProfileWithTempIdFromCursor(cursor));
+ soundProfiles.add(convertCursorToSoundProfileWithTempId(cursor));
}
return soundProfiles;
}
}
+ private void notifyError(String profileId, int errorCode, int uid, int pid) {
+ UserState userState = getOrCreateUserStateLocked(UserHandle.USER_SYSTEM);
+ int n = userState.mCallbacks.beginBroadcast();
+
+ for (int i = 0; i < n; ++i) {
+ try {
+ IPictureProfileCallback callback = userState.mCallbacks.getBroadcastItem(i);
+ Pair<Integer, Integer> pidUid = userState.mCallbackPidUidMap.get(callback);
+
+ if (pidUid.first == pid && pidUid.second == uid) {
+ userState.mCallbacks.getBroadcastItem(i).onError(profileId, errorCode);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report added input to callback", e);
+ }
+ }
+ userState.mCallbacks.finishBroadcast();
+ }
+
@Override
public void registerPictureProfileCallback(final IPictureProfileCallback callback) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+
+ UserState userState = getOrCreateUserStateLocked(Binder.getCallingUid());
+ userState.mCallbackPidUidMap.put(callback, Pair.create(callingPid, callingUid));
}
+
@Override
public void registerSoundProfileCallback(final ISoundProfileCallback callback) {
}
@Override
public void registerAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ if (!hasReadColorZonesPermission()) {
+ //TODO: error handling
+ }
}
@Override
public void setAmbientBacklightSettings(
AmbientBacklightSettings settings, UserHandle user) {
+ if (!hasReadColorZonesPermission()) {
+ //TODO: error handling
+ }
}
@Override
public void setAmbientBacklightEnabled(boolean enabled, UserHandle user) {
+ if (!hasReadColorZonesPermission()) {
+ //TODO: error handling
+ }
}
@Override
@@ -551,20 +769,34 @@ public class MediaQualityService extends SystemService {
@Override
public List<String> getPictureProfileAllowList(UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ //TODO: error handling
+ return new ArrayList<>();
+ }
return new ArrayList<>();
}
@Override
public void setPictureProfileAllowList(List<String> packages, UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ //TODO: error handling
+ }
}
@Override
public List<String> getSoundProfileAllowList(UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ return new ArrayList<>();
+ }
return new ArrayList<>();
}
@Override
public void setSoundProfileAllowList(List<String> packages, UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ }
}
@Override
@@ -574,6 +806,9 @@ public class MediaQualityService extends SystemService {
@Override
public void setAutoPictureQualityEnabled(boolean enabled, UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ //TODO: error handling
+ }
}
@Override
@@ -583,6 +818,9 @@ public class MediaQualityService extends SystemService {
@Override
public void setSuperResolutionEnabled(boolean enabled, UserHandle user) {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ //TODO: error handling
+ }
}
@Override
@@ -592,6 +830,9 @@ public class MediaQualityService extends SystemService {
@Override
public void setAutoSoundQualityEnabled(boolean enabled, UserHandle user) {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ //TODO: error handling
+ }
}
@Override
@@ -604,4 +845,38 @@ public class MediaQualityService extends SystemService {
return false;
}
}
+
+ private class MediaQualityManagerCallbackList extends
+ RemoteCallbackList<IPictureProfileCallback> {
+ @Override
+ public void onCallbackDied(IPictureProfileCallback callback) {
+ //todo
+ }
+ }
+
+ private final class UserState {
+ // A list of callbacks.
+ private final MediaQualityManagerCallbackList mCallbacks =
+ new MediaQualityManagerCallbackList();
+
+ private final Map<IPictureProfileCallback, Pair<Integer, Integer>> mCallbackPidUidMap =
+ new HashMap<>();
+
+ private UserState(Context context, int userId) {
+
+ }
+ }
+
+ private UserState getOrCreateUserStateLocked(int userId) {
+ UserState userState = getUserStateLocked(userId);
+ if (userState == null) {
+ userState = new UserState(mContext, userId);
+ mUserStates.put(userId, userState);
+ }
+ return userState;
+ }
+
+ private UserState getUserStateLocked(int userId) {
+ return mUserStates.get(userId);
+ }
}
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index 0b40d64e3a09..3f2c2228e453 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -325,7 +325,7 @@ public class ConditionProviders extends ManagedServices {
for (int i = 0; i < N; i++) {
final Condition c = conditions[i];
if (mCallback != null) {
- mCallback.onConditionChanged(c.id, c);
+ mCallback.onConditionChanged(c.id, c, info.uid);
}
}
}
@@ -515,7 +515,7 @@ public class ConditionProviders extends ManagedServices {
public interface Callback {
void onServiceAdded(ComponentName component);
- void onConditionChanged(Uri id, Condition condition);
+ void onConditionChanged(Uri id, Condition condition, int callerUid);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f50e8aa7eb7b..9567c818fa18 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5903,8 +5903,9 @@ public class NotificationManagerService extends SystemService {
// TODO: b/310620812 - Remove getZenRules() when MODES_API is inlined.
@Override
public List<ZenModeConfig.ZenRule> getZenRules() throws RemoteException {
- enforcePolicyAccess(Binder.getCallingUid(), "getZenRules");
- return mZenModeHelper.getZenRules(getCallingZenUser());
+ int callingUid = Binder.getCallingUid();
+ enforcePolicyAccess(callingUid, "getZenRules");
+ return mZenModeHelper.getZenRules(getCallingZenUser(), callingUid);
}
@Override
@@ -5912,15 +5913,17 @@ public class NotificationManagerService extends SystemService {
if (!android.app.Flags.modesApi()) {
throw new IllegalStateException("getAutomaticZenRules called with flag off!");
}
- enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRules");
- return mZenModeHelper.getAutomaticZenRules(getCallingZenUser());
+ int callingUid = Binder.getCallingUid();
+ enforcePolicyAccess(callingUid, "getAutomaticZenRules");
+ return mZenModeHelper.getAutomaticZenRules(getCallingZenUser(), callingUid);
}
@Override
public AutomaticZenRule getAutomaticZenRule(String id) throws RemoteException {
Objects.requireNonNull(id, "Id is null");
- enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRule");
- return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id);
+ int callingUid = Binder.getCallingUid();
+ enforcePolicyAccess(callingUid, "getAutomaticZenRule");
+ return mZenModeHelper.getAutomaticZenRule(getCallingZenUser(), id, callingUid);
}
@Override
@@ -6065,8 +6068,9 @@ public class NotificationManagerService extends SystemService {
@Condition.State
public int getAutomaticZenRuleState(@NonNull String id) {
Objects.requireNonNull(id, "id is null");
- enforcePolicyAccess(Binder.getCallingUid(), "getAutomaticZenRuleState");
- return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id);
+ int callingUid = Binder.getCallingUid();
+ enforcePolicyAccess(callingUid, "getAutomaticZenRuleState");
+ return mZenModeHelper.getAutomaticZenRuleState(getCallingZenUser(), id, callingUid);
}
@Override
diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java
index 52d0c41614d5..d44baeb58a28 100644
--- a/services/core/java/com/android/server/notification/ZenModeConditions.java
+++ b/services/core/java/com/android/server/notification/ZenModeConditions.java
@@ -113,15 +113,18 @@ public class ZenModeConditions implements ConditionProviders.Callback {
}
@Override
- public void onConditionChanged(Uri id, Condition condition) {
+ public void onConditionChanged(Uri id, Condition condition, int callingUid) {
if (DEBUG) Log.d(TAG, "onConditionChanged " + id + " " + condition);
ZenModeConfig config = mHelper.getConfig();
if (config == null) return;
- final int callingUid = Binder.getCallingUid();
+ if (!Flags.fixCallingUidFromCps()) {
+ // Old behavior: overwrite with known-bad callingUid (always system_server).
+ callingUid = Binder.getCallingUid();
+ }
// This change is known to be for UserHandle.CURRENT because ConditionProviders for
// background users are not bound.
- mHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, condition,
+ mHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, id, condition,
callingUid == Process.SYSTEM_UID ? ZenModeConfig.ORIGIN_SYSTEM
: ZenModeConfig.ORIGIN_APP,
callingUid);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index b571d62c0cba..0a63f3fb36d0 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -413,13 +413,13 @@ public class ZenModeHelper {
}
// TODO: b/310620812 - Make private (or inline) when MODES_API is inlined.
- public List<ZenRule> getZenRules(UserHandle user) {
+ public List<ZenRule> getZenRules(UserHandle user, int callingUid) {
List<ZenRule> rules = new ArrayList<>();
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) return rules;
for (ZenRule rule : config.automaticRules.values()) {
- if (canManageAutomaticZenRule(rule)) {
+ if (canManageAutomaticZenRule(rule, callingUid)) {
rules.add(rule);
}
}
@@ -432,8 +432,8 @@ public class ZenModeHelper {
* (which means the owned rules for a regular app, and every rule for system callers) together
* with their ids.
*/
- Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user) {
- List<ZenRule> ruleList = getZenRules(user);
+ Map<String, AutomaticZenRule> getAutomaticZenRules(UserHandle user, int callingUid) {
+ List<ZenRule> ruleList = getZenRules(user, callingUid);
HashMap<String, AutomaticZenRule> rules = new HashMap<>(ruleList.size());
for (ZenRule rule : ruleList) {
rules.put(rule.id, zenRuleToAutomaticZenRule(rule));
@@ -441,7 +441,7 @@ public class ZenModeHelper {
return rules;
}
- public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id) {
+ public AutomaticZenRule getAutomaticZenRule(UserHandle user, String id, int callingUid) {
ZenRule rule;
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
@@ -449,7 +449,7 @@ public class ZenModeHelper {
rule = config.automaticRules.get(id);
}
if (rule == null) return null;
- if (canManageAutomaticZenRule(rule)) {
+ if (canManageAutomaticZenRule(rule, callingUid)) {
return zenRuleToAutomaticZenRule(rule);
}
return null;
@@ -591,7 +591,7 @@ public class ZenModeHelper {
+ " reason=" + reason);
}
ZenModeConfig.ZenRule oldRule = config.automaticRules.get(ruleId);
- if (oldRule == null || !canManageAutomaticZenRule(oldRule)) {
+ if (oldRule == null || !canManageAutomaticZenRule(oldRule, callingUid)) {
throw new SecurityException(
"Cannot update rules not owned by your condition provider");
}
@@ -859,7 +859,7 @@ public class ZenModeHelper {
newConfig = config.copy();
ZenRule ruleToRemove = newConfig.automaticRules.get(id);
if (ruleToRemove == null) return false;
- if (canManageAutomaticZenRule(ruleToRemove)) {
+ if (canManageAutomaticZenRule(ruleToRemove, callingUid)) {
newConfig.automaticRules.remove(id);
maybePreserveRemovedRule(newConfig, ruleToRemove, origin);
if (ruleToRemove.getPkg() != null
@@ -893,7 +893,8 @@ public class ZenModeHelper {
newConfig = config.copy();
for (int i = newConfig.automaticRules.size() - 1; i >= 0; i--) {
ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
- if (Objects.equals(rule.getPkg(), packageName) && canManageAutomaticZenRule(rule)) {
+ if (Objects.equals(rule.getPkg(), packageName)
+ && canManageAutomaticZenRule(rule, callingUid)) {
newConfig.automaticRules.removeAt(i);
maybePreserveRemovedRule(newConfig, rule, origin);
}
@@ -938,14 +939,14 @@ public class ZenModeHelper {
}
@Condition.State
- int getAutomaticZenRuleState(UserHandle user, String id) {
+ int getAutomaticZenRuleState(UserHandle user, String id, int callingUid) {
synchronized (mConfigLock) {
ZenModeConfig config = getConfigLocked(user);
if (config == null) {
return Condition.STATE_UNKNOWN;
}
ZenRule rule = config.automaticRules.get(id);
- if (rule == null || !canManageAutomaticZenRule(rule)) {
+ if (rule == null || !canManageAutomaticZenRule(rule, callingUid)) {
return Condition.STATE_UNKNOWN;
}
if (Flags.modesApi() && Flags.modesUi()) {
@@ -968,7 +969,7 @@ public class ZenModeHelper {
newConfig = config.copy();
ZenRule rule = newConfig.automaticRules.get(id);
if (Flags.modesApi()) {
- if (rule != null && canManageAutomaticZenRule(rule)) {
+ if (rule != null && canManageAutomaticZenRule(rule, callingUid)) {
setAutomaticZenRuleStateLocked(newConfig, Collections.singletonList(rule),
condition, origin, callingUid);
}
@@ -980,8 +981,8 @@ public class ZenModeHelper {
}
}
- void setAutomaticZenRuleState(UserHandle user, Uri ruleDefinition, Condition condition,
- @ConfigOrigin int origin, int callingUid) {
+ void setAutomaticZenRuleStateFromConditionProvider(UserHandle user, Uri ruleDefinition,
+ Condition condition, @ConfigOrigin int origin, int callingUid) {
checkSetRuleStateOrigin("setAutomaticZenRuleState(Uri ruleDefinition)", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
@@ -992,7 +993,7 @@ public class ZenModeHelper {
List<ZenRule> matchingRules = findMatchingRules(newConfig, ruleDefinition, condition);
if (Flags.modesApi()) {
for (int i = matchingRules.size() - 1; i >= 0; i--) {
- if (!canManageAutomaticZenRule(matchingRules.get(i))) {
+ if (!canManageAutomaticZenRule(matchingRules.get(i), callingUid)) {
matchingRules.remove(i);
}
}
@@ -1125,15 +1126,21 @@ public class ZenModeHelper {
return count;
}
- public boolean canManageAutomaticZenRule(ZenRule rule) {
- final int callingUid = Binder.getCallingUid();
+ public boolean canManageAutomaticZenRule(ZenRule rule, int callingUid) {
+ if (!com.android.server.notification.Flags.fixCallingUidFromCps()) {
+ // Old behavior: ignore supplied callingUid and instead obtain it here. Will be
+ // incorrect if not currently handling a Binder call.
+ callingUid = Binder.getCallingUid();
+ }
+
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
+ // Checked specifically, because checkCallingPermission() will fail.
return true;
} else if (mContext.checkCallingPermission(android.Manifest.permission.MANAGE_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED) {
return true;
} else {
- String[] packages = mPm.getPackagesForUid(Binder.getCallingUid());
+ String[] packages = mPm.getPackagesForUid(callingUid);
if (packages != null) {
final int packageCount = packages.length;
for (int i = 0; i < packageCount; i++) {
@@ -2902,8 +2909,8 @@ public class ZenModeHelper {
}
/**
- * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} overloads makes
- * sense.
+ * Checks that the {@code origin} supplied to {@link #setAutomaticZenRuleState} or
+ * {@link #setAutomaticZenRuleStateFromConditionProvider} makes sense.
*/
private static void checkSetRuleStateOrigin(String method, @ConfigOrigin int origin) {
if (!Flags.modesApi()) {
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index f15c23e110a4..2b4d71e85dc0 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -196,4 +196,14 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
-} \ No newline at end of file
+}
+
+flag {
+ name: "fix_calling_uid_from_cps"
+ namespace: "systemui"
+ description: "Correctly checks zen rule ownership when a CPS notifies with a Condition"
+ bug: "379722187"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java
index 1b22154c10f6..d33c860343c5 100644
--- a/services/core/java/com/android/server/om/IdmapDaemon.java
+++ b/services/core/java/com/android/server/om/IdmapDaemon.java
@@ -28,6 +28,7 @@ import android.os.IBinder;
import android.os.IIdmap2;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemService;
import android.text.TextUtils;
@@ -40,7 +41,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicInteger;
/**
* To prevent idmap2d from continuously running, the idmap daemon will terminate after 10 seconds
@@ -66,7 +66,7 @@ class IdmapDaemon {
private static IdmapDaemon sInstance;
private volatile IIdmap2 mService;
- private final AtomicInteger mOpenedCount = new AtomicInteger();
+ private int mOpenedCount = 0;
private final Object mIdmapToken = new Object();
/**
@@ -74,15 +74,20 @@ class IdmapDaemon {
* finalized, the idmap service will be stopped after a period of time unless another connection
* to the service is open.
**/
- private class Connection implements AutoCloseable {
+ private final class Connection implements AutoCloseable {
@Nullable
private final IIdmap2 mIdmap2;
private boolean mOpened = true;
- private Connection(IIdmap2 idmap2) {
+ private Connection() {
+ mIdmap2 = null;
+ mOpened = false;
+ }
+
+ private Connection(@NonNull IIdmap2 idmap2) {
+ mIdmap2 = idmap2;
synchronized (mIdmapToken) {
- mOpenedCount.incrementAndGet();
- mIdmap2 = idmap2;
+ ++mOpenedCount;
}
}
@@ -94,20 +99,22 @@ class IdmapDaemon {
}
mOpened = false;
- if (mOpenedCount.decrementAndGet() != 0) {
+ if (--mOpenedCount != 0) {
// Only post the callback to stop the service if the service does not have an
// open connection.
return;
}
+ final var service = mService;
FgThread.getHandler().postDelayed(() -> {
synchronized (mIdmapToken) {
- // Only stop the service if the service does not have an open connection.
- if (mService == null || mOpenedCount.get() != 0) {
+ // Only stop the service if it's the one we were scheduled for and
+ // it does not have an open connection.
+ if (mService != service || mOpenedCount != 0) {
return;
}
- stopIdmapService();
+ stopIdmapServiceLocked();
mService = null;
}
}, mIdmapToken, SERVICE_TIMEOUT_MS);
@@ -175,6 +182,8 @@ class IdmapDaemon {
}
boolean idmapExists(String overlayPath, int userId) {
+ // The only way to verify an idmap is to read its state on disk.
+ final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try (Connection c = connect()) {
final IIdmap2 idmap2 = c.getIdmap2();
if (idmap2 == null) {
@@ -187,6 +196,8 @@ class IdmapDaemon {
} catch (Exception e) {
Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath, e);
return false;
+ } finally {
+ StrictMode.setThreadPolicy(oldPolicy);
}
}
@@ -242,14 +253,16 @@ class IdmapDaemon {
} catch (Exception e) {
Slog.wtf(TAG, "failed to get all fabricated overlays", e);
} finally {
- try {
- if (c.getIdmap2() != null && iteratorId != -1) {
- c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
+ if (c != null) {
+ try {
+ if (c.getIdmap2() != null && iteratorId != -1) {
+ c.getIdmap2().releaseFabricatedOverlayIterator(iteratorId);
+ }
+ } catch (RemoteException e) {
+ // ignore
}
- } catch (RemoteException e) {
- // ignore
+ c.close();
}
- c.close();
}
return allInfos;
}
@@ -271,9 +284,11 @@ class IdmapDaemon {
}
@Nullable
- private IBinder getIdmapService() throws TimeoutException, RemoteException {
+ private IBinder getIdmapServiceLocked() throws TimeoutException, RemoteException {
try {
- SystemService.start(IDMAP_DAEMON);
+ if (!SystemService.isRunning(IDMAP_DAEMON)) {
+ SystemService.start(IDMAP_DAEMON);
+ }
} catch (RuntimeException e) {
Slog.wtf(TAG, "Failed to enable idmap2 daemon", e);
if (e.getMessage().contains("failed to set system property")) {
@@ -306,9 +321,11 @@ class IdmapDaemon {
walltimeMillis - endWalltimeMillis + SERVICE_CONNECT_WALLTIME_TIMEOUT_MS));
}
- private static void stopIdmapService() {
+ private static void stopIdmapServiceLocked() {
try {
- SystemService.stop(IDMAP_DAEMON);
+ if (SystemService.isRunning(IDMAP_DAEMON)) {
+ SystemService.stop(IDMAP_DAEMON);
+ }
} catch (RuntimeException e) {
// If the idmap daemon cannot be disabled for some reason, it is okay
// since we already finished invoking idmap.
@@ -326,9 +343,9 @@ class IdmapDaemon {
return new Connection(mService);
}
- IBinder binder = getIdmapService();
+ IBinder binder = getIdmapServiceLocked();
if (binder == null) {
- return new Connection(null);
+ return new Connection();
}
mService = IIdmap2.Stub.asInterface(binder);
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index cc5c88b77293..d806770e5c91 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -19,7 +19,6 @@ package com.android.server.om;
import android.annotation.NonNull;
import android.content.om.OverlayInfo;
import android.content.om.OverlayableInfo;
-import android.content.res.Flags;
import android.net.Uri;
import android.os.Process;
import android.text.TextUtils;
@@ -163,15 +162,11 @@ public class OverlayActorEnforcer {
return ActorState.UNABLE_TO_GET_TARGET_OVERLAYABLE;
}
- // Framework doesn't have <overlayable> declaration by design, and we still want to be able
- // to enable its overlays from the packages with the permission.
- if (targetOverlayable == null
- && !(Flags.rroControlForAndroidNoOverlayable() && targetPackageName.equals(
- "android"))) {
+ if (targetOverlayable == null) {
return ActorState.MISSING_OVERLAYABLE;
}
- final String actor = targetOverlayable == null ? null : targetOverlayable.actor;
+ String actor = targetOverlayable.actor;
if (TextUtils.isEmpty(actor)) {
// If there's no actor defined, fallback to the legacy permission check
try {
diff --git a/services/core/java/com/android/server/om/OverlayReferenceMapper.java b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
index fdceabe74dd8..18de9952ed19 100644
--- a/services/core/java/com/android/server/om/OverlayReferenceMapper.java
+++ b/services/core/java/com/android/server/om/OverlayReferenceMapper.java
@@ -26,15 +26,13 @@ import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.CollectionUtils;
import com.android.server.SystemConfig;
import com.android.server.pm.pkg.AndroidPackage;
import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -121,20 +119,16 @@ public class OverlayReferenceMapper {
return actorPair.first;
}
- @NonNull
+ @Nullable
@Override
- public Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
+ public Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg) {
String target = pkg.getOverlayTarget();
if (TextUtils.isEmpty(target)) {
- return Collections.emptyMap();
+ return null;
}
String overlayable = pkg.getOverlayTargetOverlayableName();
- Map<String, Set<String>> targetToOverlayables = new HashMap<>();
- Set<String> overlayables = new HashSet<>();
- overlayables.add(overlayable);
- targetToOverlayables.put(target, overlayables);
- return targetToOverlayables;
+ return Pair.create(target, overlayable);
}
};
}
@@ -174,7 +168,7 @@ public class OverlayReferenceMapper {
}
// TODO(b/135203078): Replace with isOverlay boolean flag check; fix test mocks
- if (!mProvider.getTargetToOverlayables(pkg).isEmpty()) {
+ if (mProvider.getTargetToOverlayables(pkg) != null) {
addOverlay(pkg, otherPkgs, changed);
}
@@ -245,20 +239,17 @@ public class OverlayReferenceMapper {
String target = targetPkg.getPackageName();
removeTarget(target, changedPackages);
- Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
- for (String overlayable : overlayablesToActors.keySet()) {
- String actor = overlayablesToActors.get(overlayable);
+ final Map<String, String> overlayablesToActors = targetPkg.getOverlayables();
+ for (final var entry : overlayablesToActors.entrySet()) {
+ final String overlayable = entry.getKey();
+ final String actor = entry.getValue();
addTargetToMap(actor, target, changedPackages);
for (AndroidPackage overlayPkg : otherPkgs.values()) {
- Map<String, Set<String>> targetToOverlayables =
+ var targetToOverlayables =
mProvider.getTargetToOverlayables(overlayPkg);
- Set<String> overlayables = targetToOverlayables.get(target);
- if (CollectionUtils.isEmpty(overlayables)) {
- continue;
- }
-
- if (overlayables.contains(overlayable)) {
+ if (targetToOverlayables != null && targetToOverlayables.first.equals(target)
+ && Objects.equals(targetToOverlayables.second, overlayable)) {
String overlay = overlayPkg.getPackageName();
addOverlayToMap(actor, target, overlay, changedPackages);
}
@@ -310,25 +301,22 @@ public class OverlayReferenceMapper {
String overlay = overlayPkg.getPackageName();
removeOverlay(overlay, changedPackages);
- Map<String, Set<String>> targetToOverlayables =
+ Pair<String, String> targetToOverlayables =
mProvider.getTargetToOverlayables(overlayPkg);
- for (Map.Entry<String, Set<String>> entry : targetToOverlayables.entrySet()) {
- String target = entry.getKey();
- Set<String> overlayables = entry.getValue();
+ if (targetToOverlayables != null) {
+ String target = targetToOverlayables.first;
AndroidPackage targetPkg = otherPkgs.get(target);
if (targetPkg == null) {
- continue;
+ return;
}
-
String targetPkgName = targetPkg.getPackageName();
Map<String, String> overlayableToActor = targetPkg.getOverlayables();
- for (String overlayable : overlayables) {
- String actor = overlayableToActor.get(overlayable);
- if (TextUtils.isEmpty(actor)) {
- continue;
- }
- addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
+ String overlayable = targetToOverlayables.second;
+ String actor = overlayableToActor.get(overlayable);
+ if (TextUtils.isEmpty(actor)) {
+ return;
}
+ addOverlayToMap(actor, targetPkgName, overlay, changedPackages);
}
}
}
@@ -430,11 +418,11 @@ public class OverlayReferenceMapper {
String getActorPkg(@NonNull String actor);
/**
- * Mock response of multiple overlay tags.
+ * Mock response of overlay tags.
*
* TODO(b/119899133): Replace with actual implementation; fix OverlayReferenceMapperTests
*/
- @NonNull
- Map<String, Set<String>> getTargetToOverlayables(@NonNull AndroidPackage pkg);
+ @Nullable
+ Pair<String, String> getTargetToOverlayables(@NonNull AndroidPackage pkg);
}
}
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 3aefc5a64926..473ed6136e9a 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -23,6 +23,7 @@ import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.security.FileIntegrity;
import libcore.io.IoUtils;
@@ -121,6 +122,11 @@ final class ResilientAtomicFile implements Closeable {
}
public void finishWrite(FileOutputStream str) throws IOException {
+ finishWrite(str, true /* doFsVerity */);
+ }
+
+ @VisibleForTesting
+ public void finishWrite(FileOutputStream str, final boolean doFsVerity) throws IOException {
if (mMainOutStream != str) {
throw new IllegalStateException("Invalid incoming stream.");
}
@@ -145,13 +151,15 @@ final class ResilientAtomicFile implements Closeable {
finalizeOutStream(reserveOutStream);
}
- // Protect both main and reserve using fs-verity.
- try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD());
- ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) {
- FileIntegrity.setUpFsVerity(mainPfd);
- FileIntegrity.setUpFsVerity(copyPfd);
- } catch (IOException e) {
- Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e);
+ if (doFsVerity) {
+ // Protect both main and reserve using fs-verity.
+ try (ParcelFileDescriptor mainPfd = ParcelFileDescriptor.dup(mainInStream.getFD());
+ ParcelFileDescriptor copyPfd = ParcelFileDescriptor.dup(reserveInStream.getFD())) {
+ FileIntegrity.setUpFsVerity(mainPfd);
+ FileIntegrity.setUpFsVerity(copyPfd);
+ } catch (IOException e) {
+ Slog.e(LOG_TAG, "Failed to verity-protect " + mDebugName, e);
+ }
}
} catch (IOException e) {
Slog.e(LOG_TAG, "Failed to write reserve copy " + mDebugName + ": " + mReserveCopy, e);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 44789e4c4de2..027da4986ce6 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -179,7 +179,7 @@ abstract class ShortcutPackageItem {
itemOut.endDocument();
os.flush();
- file.finishWrite(os);
+ mShortcutUser.mService.injectFinishWrite(file, os);
} catch (XmlPullParserException | IOException e) {
Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(os);
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 2785da5cbdbd..373c1ed3c386 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1008,7 +1008,7 @@ public class ShortcutService extends IShortcutService.Stub {
out.endDocument();
// Close.
- file.finishWrite(outs);
+ injectFinishWrite(file, outs);
} catch (IOException e) {
Slog.w(TAG, "Failed to write to file " + file.getBaseFile(), e);
file.failWrite(outs);
@@ -1096,7 +1096,7 @@ public class ShortcutService extends IShortcutService.Stub {
saveUserInternalLocked(userId, os, /* forBackup= */ false);
}
- file.finishWrite(os);
+ injectFinishWrite(file, os);
// Remove all dangling bitmap files.
cleanupDanglingBitmapDirectoriesLocked(userId);
@@ -5067,6 +5067,12 @@ public class ShortcutService extends IShortcutService.Stub {
return Build.FINGERPRINT;
}
+ // Injection point.
+ void injectFinishWrite(@NonNull final ResilientAtomicFile file,
+ @NonNull final FileOutputStream os) throws IOException {
+ file.finishWrite(os);
+ }
+
final void wtf(String message) {
wtf(message, /* exception= */ null);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 672eb4caf798..9d840d0c0d35 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1681,8 +1681,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
// handle overflow
if (attributionChainId < 0) {
- attributionChainId = 0;
sAttributionChainIds.set(0);
+ attributionChainId = sAttributionChainIds.incrementAndGet();
}
return attributionChainId;
}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index 7a5a14d8d3c2..b32943704dc4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -293,8 +293,8 @@ public class PackageUserStateImpl extends WatchableImpl implements PackageUserSt
if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) {
return null;
}
- final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
- newPaths.addAll(mOverlayPaths);
+ final OverlayPaths.Builder newPaths = mOverlayPaths == null
+ ? new OverlayPaths.Builder() : new OverlayPaths.Builder(mOverlayPaths);
if (mSharedLibraryOverlayPaths != null) {
for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) {
newPaths.addAll(libOverlayPaths);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 5ab59657d4ce..7f511e1e2aa1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -85,15 +85,16 @@ import static android.view.contentprotection.flags.Flags.createAccessibilityOver
import static com.android.hardware.input.Flags.enableNew25q2Keycodes;
import static com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures;
+import static com.android.hardware.input.Flags.enableVoiceAccessKeyGestures;
import static com.android.hardware.input.Flags.inputManagerLifecycleSupport;
import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.modifierShortcutDump;
import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.GestureLauncherService.DOUBLE_POWER_TAP_COUNT_THRESHOLD;
import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -502,6 +503,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private TalkbackShortcutController mTalkbackShortcutController;
+ private VoiceAccessShortcutController mVoiceAccessShortcutController;
+
private WindowWakeUpPolicy mWindowWakeUpPolicy;
/**
@@ -562,8 +565,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
volatile boolean mPowerKeyHandled;
volatile boolean mBackKeyHandled;
volatile boolean mEndCallKeyHandled;
- volatile boolean mCameraGestureTriggered;
- volatile boolean mCameraGestureTriggeredDuringGoingToSleep;
+ volatile boolean mPowerButtonLaunchGestureTriggered;
+ volatile boolean mPowerButtonLaunchGestureTriggeredDuringGoingToSleep;
/**
* {@code true} if the device is entering a low-power state; {@code false otherwise}.
@@ -2265,6 +2268,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return new TalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new VoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return new WindowWakeUpPolicy(mContext);
}
@@ -2512,6 +2519,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
com.android.internal.R.integer.config_keyguardDrawnTimeout);
mKeyguardDelegate = injector.getKeyguardServiceDelegate();
mTalkbackShortcutController = injector.getTalkbackShortcutController();
+ mVoiceAccessShortcutController = injector.getVoiceAccessShortcutController();
mWindowWakeUpPolicy = injector.getWindowWakeUpPolicy();
initKeyCombinationRules();
initSingleKeyGestureRules(injector.getLooper());
@@ -4262,6 +4270,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
.isAccessibilityShortcutAvailable(false);
case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK:
return enableTalkbackAndMagnifierKeyGestures();
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ return enableVoiceAccessKeyGestures();
default:
return false;
}
@@ -4492,6 +4502,14 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
break;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS:
+ if (enableVoiceAccessKeyGestures()) {
+ if (complete) {
+ mVoiceAccessShortcutController.toggleVoiceAccess(mCurrentUserId);
+ }
+ return true;
+ }
+ break;
case KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION:
AppLaunchData data = event.getAppLaunchData();
if (complete && canLaunchApp && data != null
@@ -5893,7 +5911,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mGestureLauncherService == null) {
return false;
}
- mCameraGestureTriggered = false;
+ mPowerButtonLaunchGestureTriggered = false;
final MutableBoolean outLaunched = new MutableBoolean(false);
final boolean intercept =
mGestureLauncherService.interceptPowerKeyDown(event, interactive, outLaunched);
@@ -5903,9 +5921,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
// detector from processing the power key later on.
return intercept;
}
- mCameraGestureTriggered = true;
+ mPowerButtonLaunchGestureTriggered = true;
if (mRequestedOrSleepingDefaultDisplay) {
- mCameraGestureTriggeredDuringGoingToSleep = true;
+ mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = true;
// Wake device up early to prevent display doing redundant turning off/on stuff.
mWindowWakeUpPolicy.wakeUpFromPowerKeyCameraGesture();
}
@@ -6282,13 +6300,13 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mKeyguardDelegate != null) {
mKeyguardDelegate.onFinishedGoingToSleep(pmSleepReason,
- mCameraGestureTriggeredDuringGoingToSleep);
+ mPowerButtonLaunchGestureTriggeredDuringGoingToSleep);
}
if (mDisplayFoldController != null) {
mDisplayFoldController.finishedGoingToSleep();
}
- mCameraGestureTriggeredDuringGoingToSleep = false;
- mCameraGestureTriggered = false;
+ mPowerButtonLaunchGestureTriggeredDuringGoingToSleep = false;
+ mPowerButtonLaunchGestureTriggered = false;
}
// Called on the PowerManager's Notifier thread.
@@ -6319,10 +6337,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mDefaultDisplayRotation.updateOrientationListener();
if (mKeyguardDelegate != null) {
- mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mCameraGestureTriggered);
+ mKeyguardDelegate.onStartedWakingUp(pmWakeReason, mPowerButtonLaunchGestureTriggered);
}
- mCameraGestureTriggered = false;
+ mPowerButtonLaunchGestureTriggered = false;
}
// Called on the PowerManager's Notifier thread.
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
index 9e16a7d5e83a..efda337527d4 100644
--- a/services/core/java/com/android/server/policy/TalkbackShortcutController.java
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -18,20 +18,15 @@ package com.android.server.policy;
import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
-import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
import android.os.UserHandle;
import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
import com.android.internal.accessibility.util.AccessibilityUtils;
import com.android.internal.annotations.VisibleForTesting;
-import java.util.List;
import java.util.Set;
/**
@@ -42,7 +37,6 @@ import java.util.Set;
class TalkbackShortcutController {
private static final String TALKBACK_LABEL = "TalkBack";
private final Context mContext;
- private final PackageManager mPackageManager;
public enum ShortcutSource {
GESTURE,
@@ -51,7 +45,6 @@ class TalkbackShortcutController {
TalkbackShortcutController(Context context) {
mContext = context;
- mPackageManager = mContext.getPackageManager();
}
/**
@@ -63,7 +56,10 @@ class TalkbackShortcutController {
boolean toggleTalkback(int userId, ShortcutSource source) {
final Set<ComponentName> enabledServices =
AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
- ComponentName componentName = getTalkbackComponent();
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, TALKBACK_LABEL);
+ ;
if (componentName == null) {
return false;
}
@@ -83,21 +79,6 @@ class TalkbackShortcutController {
return isTalkbackAlreadyEnabled;
}
- private ComponentName getTalkbackComponent() {
- AccessibilityManager accessibilityManager = mContext.getSystemService(
- AccessibilityManager.class);
- List<AccessibilityServiceInfo> serviceInfos =
- accessibilityManager.getInstalledAccessibilityServiceList();
-
- for (AccessibilityServiceInfo service : serviceInfos) {
- final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
- if (isTalkback(serviceInfo)) {
- return new ComponentName(serviceInfo.packageName, serviceInfo.name);
- }
- }
- return null;
- }
-
boolean isTalkBackShortcutGestureEnabled() {
return Settings.System.getIntForUser(mContext.getContentResolver(),
Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
@@ -120,9 +101,4 @@ class TalkbackShortcutController {
ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
/* serviceEnabled= */ true);
}
-
- private boolean isTalkback(ServiceInfo info) {
- return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString())
- && (info.applicationInfo.isSystemApp() || info.applicationInfo.isUpdatedSystemApp());
- }
}
diff --git a/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
new file mode 100644
index 000000000000..a37fb1140e06
--- /dev/null
+++ b/services/core/java/com/android/server/policy/VoiceAccessShortcutController.java
@@ -0,0 +1,62 @@
+/*
+ * 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.policy;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+import com.android.internal.accessibility.util.AccessibilityUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.Set;
+
+/** This class controls voice access shortcut related operations such as toggling, querying. */
+class VoiceAccessShortcutController {
+ private static final String TAG = VoiceAccessShortcutController.class.getSimpleName();
+ private static final String VOICE_ACCESS_LABEL = "Voice Access";
+
+ private final Context mContext;
+
+ VoiceAccessShortcutController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * A function that toggles voice access service.
+ *
+ * @return whether voice access is enabled after being toggled.
+ */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ boolean toggleVoiceAccess(int userId) {
+ final Set<ComponentName> enabledServices =
+ AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
+ ComponentName componentName =
+ AccessibilityUtils.getInstalledAccessibilityServiceComponentNameByLabel(
+ mContext, VOICE_ACCESS_LABEL);
+ if (componentName == null) {
+ Slog.e(TAG, "Toggle Voice Access failed due to componentName being null");
+ return false;
+ }
+
+ boolean newState = !enabledServices.contains(componentName);
+ AccessibilityUtils.setAccessibilityServiceState(mContext, componentName, newState, userId);
+
+ return newState;
+ }
+}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index da8b01ac86fb..587447b8af26 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -198,7 +198,7 @@ public class KeyguardServiceDelegate {
if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE
|| mKeyguardState.interactiveState == INTERACTIVE_STATE_WAKING) {
mKeyguardService.onStartedWakingUp(PowerManager.WAKE_REASON_UNKNOWN,
- false /* cameraGestureTriggered */);
+ false /* powerButtonLaunchGestureTriggered */);
}
if (mKeyguardState.interactiveState == INTERACTIVE_STATE_AWAKE) {
mKeyguardService.onFinishedWakingUp();
@@ -319,10 +319,10 @@ public class KeyguardServiceDelegate {
}
public void onStartedWakingUp(
- @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+ @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) {
if (mKeyguardService != null) {
if (DEBUG) Log.v(TAG, "onStartedWakingUp()");
- mKeyguardService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+ mKeyguardService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
}
mKeyguardState.interactiveState = INTERACTIVE_STATE_WAKING;
}
@@ -383,9 +383,11 @@ public class KeyguardServiceDelegate {
}
public void onFinishedGoingToSleep(
- @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+ @PowerManager.GoToSleepReason int pmSleepReason,
+ boolean powerButtonLaunchGestureTriggered) {
if (mKeyguardService != null) {
- mKeyguardService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered);
+ mKeyguardService.onFinishedGoingToSleep(pmSleepReason,
+ powerButtonLaunchGestureTriggered);
}
mKeyguardState.interactiveState = INTERACTIVE_STATE_SLEEP;
}
diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
index cd789eaed1b3..f2342e0d5688 100644
--- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
+++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java
@@ -113,9 +113,10 @@ public class KeyguardServiceWrapper implements IKeyguardService {
@Override
public void onFinishedGoingToSleep(
- @PowerManager.GoToSleepReason int pmSleepReason, boolean cameraGestureTriggered) {
+ @PowerManager.GoToSleepReason int pmSleepReason,
+ boolean powerButtonLaunchGestureTriggered) {
try {
- mService.onFinishedGoingToSleep(pmSleepReason, cameraGestureTriggered);
+ mService.onFinishedGoingToSleep(pmSleepReason, powerButtonLaunchGestureTriggered);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
@@ -123,9 +124,9 @@ public class KeyguardServiceWrapper implements IKeyguardService {
@Override
public void onStartedWakingUp(
- @PowerManager.WakeReason int pmWakeReason, boolean cameraGestureTriggered) {
+ @PowerManager.WakeReason int pmWakeReason, boolean powerButtonLaunchGestureTriggered) {
try {
- mService.onStartedWakingUp(pmWakeReason, cameraGestureTriggered);
+ mService.onStartedWakingUp(pmWakeReason, powerButtonLaunchGestureTriggered);
} catch (RemoteException e) {
Slog.w(TAG , "Remote Exception", e);
}
diff --git a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
index 7808c4ed50a4..e09ab600a1dc 100644
--- a/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
+++ b/services/core/java/com/android/server/policy/role/RoleServicePlatformHelperImpl.java
@@ -28,7 +28,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
-import android.content.pm.Signature;
import android.os.Environment;
import android.permission.flags.Flags;
import android.provider.Settings;
@@ -312,17 +311,10 @@ public class RoleServicePlatformHelperImpl implements RoleServicePlatformHelper
DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(mdos));
packageManagerInternal.forEachInstalledPackage(pkg -> {
try {
- dataOutputStream.writeUTF(pkg.getPackageName());
- dataOutputStream.writeLong(pkg.getLongVersionCode());
+ dataOutputStream.writeUTF(pkg.getPath());
dataOutputStream.writeInt(packageManagerInternal.getApplicationEnabledState(
pkg.getPackageName(), userId));
- final Set<String> requestedPermissions = pkg.getRequestedPermissions();
- dataOutputStream.writeInt(requestedPermissions.size());
- for (String permissionName : requestedPermissions) {
- dataOutputStream.writeUTF(permissionName);
- }
-
final ArraySet<String> enabledComponents =
packageManagerInternal.getEnabledComponents(pkg.getPackageName(), userId);
final int enabledComponentsSize = CollectionUtils.size(enabledComponents);
@@ -337,10 +329,6 @@ public class RoleServicePlatformHelperImpl implements RoleServicePlatformHelper
for (int i = 0; i < disabledComponentsSize; i++) {
dataOutputStream.writeUTF(disabledComponents.valueAt(i));
}
-
- for (final Signature signature : pkg.getSigningDetails().getSignatures()) {
- dataOutputStream.write(signature.toByteArray());
- }
} catch (IOException e) {
// Never happens for MessageDigestOutputStream and DataOutputStream.
throw new AssertionError(e);
diff --git a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
index a75d110e3cd1..17739712d65a 100644
--- a/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
+++ b/services/core/java/com/android/server/resources/ResourcesManagerShellCommand.java
@@ -88,6 +88,5 @@ public class ResourcesManagerShellCommand extends ShellCommand {
out.println(" Print this help text.");
out.println(" dump <PROCESS>");
out.println(" Dump the Resources objects in use as well as the history of Resources");
-
}
}
diff --git a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
index 2088e411f842..383135233049 100644
--- a/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
+++ b/services/core/java/com/android/server/stats/pull/AggregatedMobileDataStatsPuller.java
@@ -142,11 +142,8 @@ class AggregatedMobileDataStatsPuller {
private final RateLimiter mRateLimiter;
AggregatedMobileDataStatsPuller(@NonNull NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER,
- TAG + "-AggregatedMobileDataStatsPullerInit");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-Init");
}
mRateLimiter = new RateLimiter(/* window= */ Duration.ofSeconds(1));
@@ -173,10 +170,16 @@ class AggregatedMobileDataStatsPuller {
public void noteUidProcessState(int uid, int state, long unusedElapsedRealtime,
long unusedUptime) {
- mMobileDataStatsHandler.post(
+ if (mRateLimiter.tryAcquire()) {
+ mMobileDataStatsHandler.post(
() -> {
noteUidProcessStateImpl(uid, state);
});
+ } else {
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
+ }
+ }
}
public int pullDataBytesTransfer(List<StatsEvent> data) {
@@ -209,29 +212,27 @@ class AggregatedMobileDataStatsPuller {
}
private void noteUidProcessStateImpl(int uid, int state) {
- if (mRateLimiter.tryAcquire()) {
- // noteUidProcessStateImpl can be called back to back several times while
- // the updateNetworkStats loops over several stats for multiple uids
- // and during the first call in a batch of proc state change event it can
- // contain info for uid with unknown previous state yet which can happen due to a few
- // reasons:
- // - app was just started
- // - app was started before the ActivityManagerService
- // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
- if (mNetworkStatsManager != null) {
- updateNetworkStats(mNetworkStatsManager);
- } else {
- Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
- }
+ // noteUidProcessStateImpl can be called back to back several times while
+ // the updateNetworkStats loops over several stats for multiple uids
+ // and during the first call in a batch of proc state change event it can
+ // contain info for uid with unknown previous state yet which can happen due to a few
+ // reasons:
+ // - app was just started
+ // - app was started before the ActivityManagerService
+ // as result stats would be created with state == ActivityManager.PROCESS_STATE_UNKNOWN
+ if (mNetworkStatsManager != null) {
+ updateNetworkStats(mNetworkStatsManager);
+ } else {
+ Slog.w(TAG, "noteUidProcessStateLocked() can not get mNetworkStatsManager");
+ }
+ synchronized (mLock) {
+ mUidPreviousState.put(uid, state);
}
- mUidPreviousState.put(uid, state);
}
private void updateNetworkStats(NetworkStatsManager networkStatsManager) {
- if (DEBUG) {
- if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
- Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
- }
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStats");
}
final NetworkStats latestStats = networkStatsManager.getMobileUidStats();
@@ -256,20 +257,25 @@ class AggregatedMobileDataStatsPuller {
}
private void updateNetworkStatsDelta(NetworkStats delta) {
+ if (DEBUG && Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, TAG + "-updateNetworkStatsDelta");
+ }
synchronized (mLock) {
for (NetworkStats.Entry entry : delta) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
- }
- MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
- if (stats != null) {
- stats.addTxBytes(entry.getTxBytes());
- stats.addRxBytes(entry.getRxBytes());
- stats.addTxPackets(entry.getTxPackets());
- stats.addRxPackets(entry.getRxPackets());
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ MobileDataStats stats = getUidStatsForPreviousStateLocked(entry.getUid());
+ if (stats != null) {
+ stats.addTxBytes(entry.getTxBytes());
+ stats.addRxBytes(entry.getRxBytes());
+ stats.addTxPackets(entry.getTxPackets());
+ stats.addRxPackets(entry.getRxPackets());
+ }
}
}
}
+ if (DEBUG) {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
}
@GuardedBy("mLock")
@@ -298,18 +304,12 @@ class AggregatedMobileDataStatsPuller {
}
private static boolean isEmpty(NetworkStats stats) {
- long totalRxPackets = 0;
- long totalTxPackets = 0;
for (NetworkStats.Entry entry : stats) {
- if (entry.getRxPackets() == 0 && entry.getTxPackets() == 0) {
- continue;
+ if (entry.getRxPackets() != 0 || entry.getTxPackets() != 0) {
+ // at least one non empty entry located
+ return false;
}
- totalRxPackets += entry.getRxPackets();
- totalTxPackets += entry.getTxPackets();
- // at least one non empty entry located
- break;
}
- final long totalPackets = totalRxPackets + totalTxPackets;
- return totalPackets == 0;
+ return true;
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 093df8c5075c..29f1f93a844f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3209,7 +3209,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
true /* forActivity */)) {
return false;
}
- if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+ if (mAppCompatController.getResizeOverrides().allowRestrictedResizability()) {
return false;
}
// If the user preference respects aspect ratio, then it becomes non-resizable.
@@ -3240,8 +3240,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// The caller will check both application and activity level property.
return true;
}
- return !AppCompatController.allowRestrictedResizability(wms.mContext.getPackageManager(),
- appInfo.packageName);
+ return !AppCompatResizeOverrides.allowRestrictedResizability(
+ wms.mContext.getPackageManager(), appInfo.packageName);
}
boolean isResizeable() {
@@ -8435,8 +8435,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
@ActivityInfo.SizeChangesSupportMode
private int supportsSizeChanges() {
- if (mAppCompatController.getAppCompatResizeOverrides()
- .shouldOverrideForceNonResizeApp()) {
+ final AppCompatResizeOverrides resizeOverrides = mAppCompatController.getResizeOverrides();
+ if (resizeOverrides.shouldOverrideForceNonResizeApp()) {
return SIZE_CHANGES_UNSUPPORTED_OVERRIDE;
}
@@ -8444,8 +8444,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return SIZE_CHANGES_SUPPORTED_METADATA;
}
- if (mAppCompatController.getAppCompatResizeOverrides()
- .shouldOverrideForceResizeApp()) {
+ if (resizeOverrides.shouldOverrideForceResizeApp()) {
return SIZE_CHANGES_SUPPORTED_OVERRIDE;
}
@@ -10221,7 +10220,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAppCompatController.getAppCompatOrientationOverrides()
.shouldIgnoreOrientationRequestLoop());
proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
- mAppCompatController.getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+ mAppCompatController.getResizeOverrides().shouldOverrideForceResizeApp());
proto.write(SHOULD_ENABLE_USER_ASPECT_RATIO_SETTINGS,
mAppCompatController.getAppCompatAspectRatioOverrides()
.shouldEnableUserAspectRatioSettings());
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 4433d64f0d00..0967078deac3 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,23 +15,17 @@
*/
package com.android.server.wm;
-import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
-
import android.annotation.NonNull;
import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
import java.io.PrintWriter;
-import java.util.function.BooleanSupplier;
/**
* Allows the interaction with all the app compat policies and configurations
*/
class AppCompatController {
-
- @NonNull
- private final ActivityRecord mActivityRecord;
@NonNull
private final TransparentPolicy mTransparentPolicy;
@NonNull
@@ -50,56 +44,28 @@ class AppCompatController {
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
@NonNull
private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
- @NonNull
- final BooleanSupplier mAllowRestrictedResizability;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
- mActivityRecord = activityRecord;
final PackageManager packageManager = wmService.mContext.getPackageManager();
final OptPropFactory optPropBuilder = new OptPropFactory(packageManager,
activityRecord.packageName);
mAppCompatDeviceStateQuery = new AppCompatDeviceStateQuery(activityRecord);
mTransparentPolicy = new TransparentPolicy(activityRecord,
wmService.mAppCompatConfiguration);
- mAppCompatOverrides = new AppCompatOverrides(activityRecord,
+ mAppCompatOverrides = new AppCompatOverrides(activityRecord, packageManager,
wmService.mAppCompatConfiguration, optPropBuilder, mAppCompatDeviceStateQuery);
mOrientationPolicy = new AppCompatOrientationPolicy(activityRecord, mAppCompatOverrides);
mAppCompatAspectRatioPolicy = new AppCompatAspectRatioPolicy(activityRecord,
mTransparentPolicy, mAppCompatOverrides);
- mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(mActivityRecord,
+ mAppCompatReachabilityPolicy = new AppCompatReachabilityPolicy(activityRecord,
wmService.mAppCompatConfiguration);
- mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord,
+ mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(activityRecord,
wmService.mAppCompatConfiguration);
mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
- mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
+ mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(activityRecord,
mAppCompatOverrides);
- mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
- // Application level.
- if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) {
- return true;
- }
- // Activity level.
- try {
- return packageManager.getPropertyAsUser(
- PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
- mActivityRecord.mActivityComponent.getPackageName(),
- mActivityRecord.mActivityComponent.getClassName(),
- mActivityRecord.mUserId).getBoolean();
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- });
- }
-
- static boolean allowRestrictedResizability(PackageManager pm, String packageName) {
- try {
- return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName)
- .getBoolean();
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
}
@NonNull
@@ -138,8 +104,8 @@ class AppCompatController {
}
@NonNull
- AppCompatResizeOverrides getAppCompatResizeOverrides() {
- return mAppCompatOverrides.getAppCompatResizeOverrides();
+ AppCompatResizeOverrides getResizeOverrides() {
+ return mAppCompatOverrides.getResizeOverrides();
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatOverrides.java b/services/core/java/com/android/server/wm/AppCompatOverrides.java
index 2f03105846bd..58b37becc373 100644
--- a/services/core/java/com/android/server/wm/AppCompatOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatOverrides.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import android.annotation.NonNull;
+import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
@@ -34,13 +35,14 @@ public class AppCompatOverrides {
@NonNull
private final AppCompatFocusOverrides mAppCompatFocusOverrides;
@NonNull
- private final AppCompatResizeOverrides mAppCompatResizeOverrides;
+ private final AppCompatResizeOverrides mResizeOverrides;
@NonNull
private final AppCompatReachabilityOverrides mAppCompatReachabilityOverrides;
@NonNull
private final AppCompatLetterboxOverrides mAppCompatLetterboxOverrides;
AppCompatOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull PackageManager packageManager,
@NonNull AppCompatConfiguration appCompatConfiguration,
@NonNull OptPropFactory optPropBuilder,
@NonNull AppCompatDeviceStateQuery appCompatDeviceStateQuery) {
@@ -55,7 +57,8 @@ public class AppCompatOverrides {
mAppCompatReachabilityOverrides);
mAppCompatFocusOverrides = new AppCompatFocusOverrides(activityRecord,
appCompatConfiguration, optPropBuilder);
- mAppCompatResizeOverrides = new AppCompatResizeOverrides(activityRecord, optPropBuilder);
+ mResizeOverrides = new AppCompatResizeOverrides(activityRecord, packageManager,
+ optPropBuilder);
mAppCompatLetterboxOverrides = new AppCompatLetterboxOverrides(activityRecord,
appCompatConfiguration);
}
@@ -81,8 +84,8 @@ public class AppCompatOverrides {
}
@NonNull
- AppCompatResizeOverrides getAppCompatResizeOverrides() {
- return mAppCompatResizeOverrides;
+ AppCompatResizeOverrides getResizeOverrides() {
+ return mResizeOverrides;
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
index 60c18254eca7..fa53153dd143 100644
--- a/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatResizeOverrides.java
@@ -19,13 +19,17 @@ package com.android.server.wm;
import static android.content.pm.ActivityInfo.FORCE_NON_RESIZE_APP;
import static android.content.pm.ActivityInfo.FORCE_RESIZE_APP;
import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY;
import static com.android.server.wm.AppCompatUtils.isChangeEnabled;
import android.annotation.NonNull;
+import android.content.pm.PackageManager;
import com.android.server.wm.utils.OptPropFactory;
+import java.util.function.BooleanSupplier;
+
/**
* Encapsulate app compat logic about resizability.
*/
@@ -37,11 +41,40 @@ class AppCompatResizeOverrides {
@NonNull
private final OptPropFactory.OptProp mAllowForceResizeOverrideOptProp;
+ @NonNull
+ private final BooleanSupplier mAllowRestrictedResizability;
+
AppCompatResizeOverrides(@NonNull ActivityRecord activityRecord,
+ @NonNull PackageManager packageManager,
@NonNull OptPropFactory optPropBuilder) {
mActivityRecord = activityRecord;
mAllowForceResizeOverrideOptProp = optPropBuilder.create(
PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
+ mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+ // Application level.
+ if (allowRestrictedResizability(packageManager, mActivityRecord.packageName)) {
+ return true;
+ }
+ // Activity level.
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ });
+ }
+
+ static boolean allowRestrictedResizability(PackageManager pm, String packageName) {
+ try {
+ return pm.getProperty(PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY, packageName)
+ .getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
}
/**
@@ -75,4 +108,9 @@ class AppCompatResizeOverrides {
return mAllowForceResizeOverrideOptProp.shouldEnableWithOptInOverrideAndOptOutProperty(
isChangeEnabled(mActivityRecord, FORCE_NON_RESIZE_APP));
}
+
+ /** @see android.view.WindowManager#PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY */
+ boolean allowRestrictedResizability() {
+ return mAllowRestrictedResizability.getAsBoolean();
+ }
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 98ed6f76b2f9..54ae80cfe98a 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -103,6 +103,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// again, so that the control with leash can be eventually dispatched
if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending
&& mControlTarget != null) {
+ ProtoLog.d(WM_DEBUG_IME,
+ "onPostLayout: IME control ready to be dispatched, ws=%s", ws);
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
@@ -118,6 +120,8 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
} else if (wasServerVisible && !isServerVisible()) {
+ ProtoLog.d(WM_DEBUG_IME, "onPostLayout: setImeShowing(false) was: %s, ws=%s",
+ isImeShowing(), ws);
setImeShowing(false);
}
}
@@ -621,6 +625,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// request (cancelling the initial show) or hide request (aborting the initial show).
logIsScheduledAndReadyToShowIme(!visible /* aborted */);
}
+ ProtoLog.d(WM_DEBUG_IME, "receiveImeStatsToken: visible=%s", visible);
if (visible) {
ImeTracker.forLogging().onCancelled(
mStatsToken, ImeTracker.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT);
diff --git a/services/core/java/com/android/server/wm/InputConfigAdapter.java b/services/core/java/com/android/server/wm/InputConfigAdapter.java
index ae6e72464555..e3ffe716271c 100644
--- a/services/core/java/com/android/server/wm/InputConfigAdapter.java
+++ b/services/core/java/com/android/server/wm/InputConfigAdapter.java
@@ -76,9 +76,6 @@ class InputConfigAdapter {
LayoutParams.FLAG_NOT_TOUCHABLE,
InputConfig.NOT_TOUCHABLE, false /* inverted */),
new FlagMapping(
- LayoutParams.FLAG_SPLIT_TOUCH,
- InputConfig.PREVENT_SPLITTING, true /* inverted */),
- new FlagMapping(
LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
InputConfig.WATCH_OUTSIDE_TOUCH, false /* inverted */),
new FlagMapping(
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f0faa8e4691f..a9646783b92d 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1412,6 +1412,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (!tr.isAttached() || !tr.isVisibleRequested()
|| !tr.inPinnedWindowingMode()) return;
final ActivityRecord currTop = tr.getTopNonFinishingActivity();
+ if (currTop == null) return;
if (currTop.inPinnedWindowingMode()) return;
Slog.e(TAG, "Enter-PIP was started but not completed, this is a Shell/SysUI"
+ " bug. This state breaks gesture-nav, so attempting clean-up.");
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 65cf4ee733dd..911c686c711f 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -343,9 +343,10 @@ public:
void setPointerDisplayId(ui::LogicalDisplayId displayId);
int32_t getMousePointerSpeed();
void setPointerSpeed(int32_t speed);
- void setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId, bool enabled);
+ void setMouseScalingEnabled(ui::LogicalDisplayId displayId, bool enabled);
void setMouseReverseVerticalScrollingEnabled(bool enabled);
void setMouseScrollingAccelerationEnabled(bool enabled);
+ void setMouseScrollingSpeed(int32_t speed);
void setMouseSwapPrimaryButtonEnabled(bool enabled);
void setMouseAccelerationEnabled(bool enabled);
void setTouchpadPointerSpeed(int32_t speed);
@@ -473,8 +474,8 @@ private:
// Pointer speed.
int32_t pointerSpeed{0};
- // Displays on which its associated mice will have pointer acceleration disabled.
- std::set<ui::LogicalDisplayId> displaysWithMousePointerAccelerationDisabled{};
+ // Displays on which its associated mice will have all scaling disabled.
+ std::set<ui::LogicalDisplayId> displaysWithMouseScalingDisabled{};
// True if pointer gestures are enabled.
bool pointerGesturesEnabled{true};
@@ -500,6 +501,9 @@ private:
// True if mouse scrolling acceleration is enabled.
bool mouseScrollingAccelerationEnabled{true};
+ // The mouse scrolling speed, as a number from -7 (slowest) to 7 (fastest).
+ int32_t mouseScrollingSpeed{0};
+
// True if mouse vertical scrolling is reversed.
bool mouseReverseVerticalScrollingEnabled{false};
@@ -599,9 +603,8 @@ void NativeInputManager::dump(std::string& dump) {
dump += StringPrintf(INDENT "System UI Lights Out: %s\n",
toString(mLocked.systemUiLightsOut));
dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed);
- dump += StringPrintf(INDENT "Display with Mouse Pointer Acceleration Disabled: %s\n",
- dumpSet(mLocked.displaysWithMousePointerAccelerationDisabled,
- streamableToString)
+ dump += StringPrintf(INDENT "Display with Mouse Scaling Disabled: %s\n",
+ dumpSet(mLocked.displaysWithMouseScalingDisabled, streamableToString)
.c_str());
dump += StringPrintf(INDENT "Pointer Gestures Enabled: %s\n",
toString(mLocked.pointerGesturesEnabled));
@@ -830,19 +833,20 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
std::scoped_lock _l(mLock);
outConfig->mousePointerSpeed = mLocked.pointerSpeed;
- outConfig->displaysWithMousePointerAccelerationDisabled =
- mLocked.displaysWithMousePointerAccelerationDisabled;
+ outConfig->displaysWithMouseScalingDisabled = mLocked.displaysWithMouseScalingDisabled;
outConfig->pointerVelocityControlParameters.scale =
exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
- mLocked.displaysWithMousePointerAccelerationDisabled.count(
- mLocked.pointerDisplayId) == 0
+ mLocked.displaysWithMouseScalingDisabled.count(mLocked.pointerDisplayId) == 0
? android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION
: 1;
outConfig->wheelVelocityControlParameters.acceleration =
mLocked.mouseScrollingAccelerationEnabled
? android::os::IInputConstants::DEFAULT_MOUSE_WHEEL_ACCELERATION
: 1;
+ outConfig->wheelVelocityControlParameters.scale = mLocked.mouseScrollingAccelerationEnabled
+ ? 1
+ : exp2f(mLocked.mouseScrollingSpeed * POINTER_SPEED_EXPONENT);
outConfig->pointerGesturesEnabled = mLocked.pointerGesturesEnabled;
outConfig->pointerCaptureRequest = mLocked.pointerCaptureRequest;
@@ -1451,6 +1455,21 @@ void NativeInputManager::setMouseScrollingAccelerationEnabled(bool enabled) {
InputReaderConfiguration::Change::POINTER_SPEED);
}
+void NativeInputManager::setMouseScrollingSpeed(int32_t speed) {
+ { // acquire lock
+ std::scoped_lock _l(mLock);
+
+ if (mLocked.mouseScrollingSpeed == speed) {
+ return;
+ }
+
+ mLocked.mouseScrollingSpeed = speed;
+ } // release lock
+
+ mInputManager->getReader().requestRefreshConfiguration(
+ InputReaderConfiguration::Change::POINTER_SPEED);
+}
+
void NativeInputManager::setMouseSwapPrimaryButtonEnabled(bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
@@ -1497,23 +1516,21 @@ void NativeInputManager::setPointerSpeed(int32_t speed) {
InputReaderConfiguration::Change::POINTER_SPEED);
}
-void NativeInputManager::setMousePointerAccelerationEnabled(ui::LogicalDisplayId displayId,
- bool enabled) {
+void NativeInputManager::setMouseScalingEnabled(ui::LogicalDisplayId displayId, bool enabled) {
{ // acquire lock
std::scoped_lock _l(mLock);
- const bool oldEnabled =
- mLocked.displaysWithMousePointerAccelerationDisabled.count(displayId) == 0;
+ const bool oldEnabled = mLocked.displaysWithMouseScalingDisabled.count(displayId) == 0;
if (oldEnabled == enabled) {
return;
}
- ALOGI("Setting mouse pointer acceleration to %s on display %s", toString(enabled),
+ ALOGI("Setting mouse pointer scaling to %s on display %s", toString(enabled),
displayId.toString().c_str());
if (enabled) {
- mLocked.displaysWithMousePointerAccelerationDisabled.erase(displayId);
+ mLocked.displaysWithMouseScalingDisabled.erase(displayId);
} else {
- mLocked.displaysWithMousePointerAccelerationDisabled.emplace(displayId);
+ mLocked.displaysWithMouseScalingDisabled.emplace(displayId);
}
} // release lock
@@ -2567,11 +2584,11 @@ static void nativeSetPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed
im->setPointerSpeed(speed);
}
-static void nativeSetMousePointerAccelerationEnabled(JNIEnv* env, jobject nativeImplObj,
- jint displayId, jboolean enabled) {
+static void nativeSetMouseScalingEnabled(JNIEnv* env, jobject nativeImplObj, jint displayId,
+ jboolean enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
- im->setMousePointerAccelerationEnabled(ui::LogicalDisplayId{displayId}, enabled);
+ im->setMouseScalingEnabled(ui::LogicalDisplayId{displayId}, enabled);
}
static void nativeSetTouchpadPointerSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
@@ -3243,6 +3260,11 @@ static void nativeSetMouseScrollingAccelerationEnabled(JNIEnv* env, jobject nati
im->setMouseScrollingAccelerationEnabled(enabled);
}
+static void nativeSetMouseScrollingSpeed(JNIEnv* env, jobject nativeImplObj, jint speed) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ im->setMouseScrollingSpeed(speed);
+}
+
static void nativeSetMouseReverseVerticalScrollingEnabled(JNIEnv* env, jobject nativeImplObj,
bool enabled) {
NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -3313,12 +3335,12 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"transferTouch", "(Landroid/os/IBinder;I)Z", (void*)nativeTransferTouchOnDisplay},
{"getMousePointerSpeed", "()I", (void*)nativeGetMousePointerSpeed},
{"setPointerSpeed", "(I)V", (void*)nativeSetPointerSpeed},
- {"setMousePointerAccelerationEnabled", "(IZ)V",
- (void*)nativeSetMousePointerAccelerationEnabled},
+ {"setMouseScalingEnabled", "(IZ)V", (void*)nativeSetMouseScalingEnabled},
{"setMouseReverseVerticalScrollingEnabled", "(Z)V",
(void*)nativeSetMouseReverseVerticalScrollingEnabled},
{"setMouseScrollingAccelerationEnabled", "(Z)V",
(void*)nativeSetMouseScrollingAccelerationEnabled},
+ {"setMouseScrollingSpeed", "(I)V", (void*)nativeSetMouseScrollingSpeed},
{"setMouseSwapPrimaryButtonEnabled", "(Z)V", (void*)nativeSetMouseSwapPrimaryButtonEnabled},
{"setMouseAccelerationEnabled", "(Z)V", (void*)nativeSetMouseAccelerationEnabled},
{"setTouchpadPointerSpeed", "(I)V", (void*)nativeSetTouchpadPointerSpeed},
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2627895b8c63..e69a7414dd76 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -21907,7 +21907,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
accountToMigrate,
sourceUser,
targetUser,
- /* callback= */ null, /* handler= */ null)
+ /* handler= */ null, /* callback= */ null)
.getResult(60 * 3, TimeUnit.SECONDS);
if (copySucceeded) {
logCopyAccountStatus(COPY_ACCOUNT_SUCCEEDED, callerPackage);
diff --git a/services/print/Android.bp b/services/print/Android.bp
index 0dfceaa3a9d9..b77cf162d984 100644
--- a/services/print/Android.bp
+++ b/services/print/Android.bp
@@ -18,8 +18,21 @@ java_library_static {
name: "services.print",
defaults: ["platform_service_defaults"],
srcs: [":services.print-sources"],
+ static_libs: ["print_flags_lib"],
libs: ["services.core"],
lint: {
baseline_filename: "lint-baseline.xml",
},
}
+
+aconfig_declarations {
+ name: "print_flags",
+ package: "com.android.server.print",
+ container: "system",
+ srcs: ["**/flags.aconfig"],
+}
+
+java_aconfig_library {
+ name: "print_flags_lib",
+ aconfig_declarations: "print_flags",
+}
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index 502cd2c60f4a..b85671581cc5 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -572,7 +572,8 @@ final class RemotePrintService implements DeathRecipient {
boolean wasBound = mContext.bindServiceAsUser(mIntent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
- | Context.BIND_INCLUDE_CAPABILITIES | Context.BIND_ALLOW_INSTANT,
+ | (Flags.doNotIncludeCapabilities() ? 0 : Context.BIND_INCLUDE_CAPABILITIES)
+ | Context.BIND_ALLOW_INSTANT,
new UserHandle(mUserId));
if (!wasBound) {
diff --git a/services/print/java/com/android/server/print/flags.aconfig b/services/print/java/com/android/server/print/flags.aconfig
new file mode 100644
index 000000000000..0210791cfeda
--- /dev/null
+++ b/services/print/java/com/android/server/print/flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.print"
+container: "system"
+
+flag {
+ name: "do_not_include_capabilities"
+ namespace: "print"
+ description: "Do not use the flag Context.BIND_INCLUDE_CAPABILITIES when binding to the service"
+ bug: "291281543"
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 7277fd79fdd5..66aaa562b873 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -45,6 +45,7 @@ import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Pair;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -78,10 +79,7 @@ import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
@Presubmit
@RunWith(JUnit4.class)
@@ -885,18 +883,15 @@ public class AppsFilterImplTest {
return null;
}
- @NonNull
+ @Nullable
@Override
- public Map<String, Set<String>> getTargetToOverlayables(
+ public Pair<String, String> getTargetToOverlayables(
@NonNull AndroidPackage pkg) {
if (overlay.getPackageName().equals(pkg.getPackageName())) {
- Map<String, Set<String>> map = new ArrayMap<>();
- Set<String> set = new ArraySet<>();
- set.add(overlay.getOverlayTargetOverlayableName());
- map.put(overlay.getOverlayTarget(), set);
- return map;
+ return Pair.create(overlay.getOverlayTarget(),
+ overlay.getOverlayTargetOverlayableName());
}
- return Collections.emptyMap();
+ return null;
}
},
mMockHandler);
@@ -977,18 +972,15 @@ public class AppsFilterImplTest {
return null;
}
- @NonNull
+ @Nullable
@Override
- public Map<String, Set<String>> getTargetToOverlayables(
+ public Pair<String, String> getTargetToOverlayables(
@NonNull AndroidPackage pkg) {
if (overlay.getPackageName().equals(pkg.getPackageName())) {
- Map<String, Set<String>> map = new ArrayMap<>();
- Set<String> set = new ArraySet<>();
- set.add(overlay.getOverlayTargetOverlayableName());
- map.put(overlay.getOverlayTarget(), set);
- return map;
+ return Pair.create(overlay.getOverlayTarget(),
+ overlay.getOverlayTargetOverlayableName());
}
- return Collections.emptyMap();
+ return null;
}
},
mMockHandler);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
index 89b48bad2358..27eada013642 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/PendingIntentControllerTest.java
@@ -16,6 +16,8 @@
package com.android.server.am;
+import static android.os.PowerWhitelistManager.REASON_NOTIFICATION_SERVICE;
+import static android.os.PowerWhitelistManager.TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.Process.INVALID_UID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -27,9 +29,11 @@ import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_CANC
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_OWNER_FORCE_STOPPED;
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_SUPERSEDED;
import static com.android.server.am.PendingIntentRecord.CANCEL_REASON_USER_STOPPED;
+import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.cancelReasonToString;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -39,9 +43,11 @@ import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppGlobals;
+import android.app.BackgroundStartPrivileges;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.IPackageManager;
+import android.os.Binder;
import android.os.Looper;
import android.os.UserHandle;
@@ -179,6 +185,34 @@ public class PendingIntentControllerTest {
}
}
+ @Test
+ public void testClearAllowBgActivityStartsClearsToken() {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ Binder token = new Binder();
+ pir.setAllowBgActivityStarts(token, FLAG_ACTIVITY_SENDER);
+ assertEquals(BackgroundStartPrivileges.allowBackgroundActivityStarts(token),
+ pir.getBackgroundStartPrivilegesForActivitySender(token));
+ pir.clearAllowBgActivityStarts(token);
+ assertEquals(BackgroundStartPrivileges.NONE,
+ pir.getBackgroundStartPrivilegesForActivitySender(token));
+ }
+
+ @Test
+ public void testClearAllowBgActivityStartsClearsDuration() {
+ final PendingIntentRecord pir = createPendingIntentRecord(0);
+ Binder token = new Binder();
+ pir.setAllowlistDurationLocked(token, 1000,
+ TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE,
+ "NotificationManagerService");
+ PendingIntentRecord.TempAllowListDuration allowlistDurationLocked =
+ pir.getAllowlistDurationLocked(token);
+ assertEquals(1000, allowlistDurationLocked.duration);
+ pir.clearAllowBgActivityStarts(token);
+ PendingIntentRecord.TempAllowListDuration allowlistDurationLockedAfterClear =
+ pir.getAllowlistDurationLocked(token);
+ assertNull(allowlistDurationLockedAfterClear);
+ }
+
private void assertCancelReason(int expectedReason, int actualReason) {
final String errMsg = "Expected: " + cancelReasonToString(expectedReason)
+ "; Actual: " + cancelReasonToString(actualReason);
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index 82efae45e1a4..92c6db5b7b96 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -21,6 +21,9 @@ import static android.service.quickaccesswallet.Flags.FLAG_LAUNCH_WALLET_VIA_SYS
import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks;
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_DISABLED_MODE;
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE;
+import static com.android.server.GestureLauncherService.DOUBLE_TAP_POWER_MULTI_TARGET_MODE;
import static com.android.server.GestureLauncherService.LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER;
import static com.android.server.GestureLauncherService.LAUNCH_WALLET_ON_DOUBLE_TAP_POWER;
import static com.android.server.GestureLauncherService.POWER_DOUBLE_TAP_MAX_TIME_MS;
@@ -163,7 +166,7 @@ public class GestureLauncherServiceTest {
new GestureLauncherService(
mContext, mMetricsLogger, mQuickAccessWalletClient, mUiEventLogger);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
}
@@ -215,68 +218,117 @@ public class GestureLauncherServiceTest {
}
@Test
- public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingDisabled() {
+ withDoubleTapPowerModeConfigValue(
+ DOUBLE_TAP_POWER_DISABLED_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
- public void testIsCameraDoubleTapPowerSettingEnabled_configFalseSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(0);
- assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
- mContext, FAKE_USER_ID));
- }
+ @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingDisabled() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
}
@Test
- public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingDisabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configFalseSettingEnabled() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
- public void testIsCameraDoubleTapPowerSettingEnabled_configTrueSettingEnabled() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- }
+ @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configFalseSettingEnabled() {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingDisabled() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingDisabled() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+
+ assertFalse(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagEnabled_configTrueSettingEnabled() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
+
+ assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsDisabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_flagDisabled_configTrueSettingEnabled() {
+ withCameraDoubleTapPowerEnableConfigValue(true);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+
assertTrue(mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@Test
@RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingEnabled() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE);
+ withCameraDoubleTapPowerDisableSettingValue(0);
+
+ assertTrue(
+ mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
+ public void testIsCameraDoubleTapPowerSettingEnabled_launchCameraMode_settingDisabled() {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_LAUNCH_CAMERA_MODE);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+
+ assertFalse(
+ mGestureLauncherService.isCameraDoubleTapPowerSettingEnabled(
+ mContext, FAKE_USER_ID));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
public void testIsCameraDoubleTapPowerSettingEnabled_actionWallet() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertFalse(
@@ -287,8 +339,8 @@ public class GestureLauncherServiceTest {
@Test
@RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
public void testIsWalletDoubleTapPowerSettingEnabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertTrue(
@@ -299,11 +351,11 @@ public class GestureLauncherServiceTest {
@Test
@RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
public void testIsWalletDoubleTapPowerSettingEnabled_configDisabled() {
- withDoubleTapPowerEnabledConfigValue(false);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
- assertTrue(
+ assertFalse(
mGestureLauncherService.isWalletDoubleTapPowerSettingEnabled(
mContext, FAKE_USER_ID));
}
@@ -311,8 +363,8 @@ public class GestureLauncherServiceTest {
@Test
@RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
public void testIsWalletDoubleTapPowerSettingEnabled_settingDisabled() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(false);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
assertFalse(
@@ -323,8 +375,8 @@ public class GestureLauncherServiceTest {
@Test
@RequiresFlagsEnabled(FLAG_LAUNCH_WALLET_OPTION_ON_POWER_DOUBLE_TAP)
public void testIsWalletDoubleTapPowerSettingEnabled_actionCamera() {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
assertFalse(
@@ -449,13 +501,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -498,13 +544,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -549,9 +589,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1031,9 +1069,7 @@ public class GestureLauncherServiceTest {
public void
testInterceptPowerKeyDown_triggerEmergency_cameraGestureEnabled_doubleTap_cooldownTriggered() {
// Enable camera double tap gesture
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
// Enable power button cooldown
withEmergencyGesturePowerButtonCooldownPeriodMsValue(3000);
@@ -1220,10 +1256,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_longpress() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
- withUserSetupCompleteValue(true);
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1400,13 +1433,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalInBoundsCameraPowerGestureOffNotInteractive() {
- if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerGestureEnableSettingValue(false);
- } else {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- }
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1449,9 +1476,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOffNotInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1495,9 +1520,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalOutOfBoundsCameraPowerGestureOffNotInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(false);
- withCameraDoubleTapPowerDisableSettingValue(1);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ disableDoubleTapPowerGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1630,9 +1653,7 @@ public class GestureLauncherServiceTest {
@Test
public void testInterceptPowerKeyDown_intervalMidBoundsCameraPowerGestureOnNotInteractive() {
- withCameraDoubleTapPowerEnableConfigValue(true);
- withCameraDoubleTapPowerDisableSettingValue(0);
- mGestureLauncherService.updateCameraDoubleTapPowerEnabled();
+ enableCameraGesture();
long eventTime = INITIAL_EVENT_TIME_MILLIS;
KeyEvent keyEvent = new KeyEvent(IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE,
@@ -1823,12 +1844,13 @@ public class GestureLauncherServiceTest {
.thenReturn(enableConfigValue);
}
- private void withDoubleTapPowerEnabledConfigValue(boolean enable) {
- when(mResources.getBoolean(com.android.internal.R.bool.config_doubleTapPowerGestureEnabled))
- .thenReturn(enable);
+ private void withDoubleTapPowerModeConfigValue(
+ int modeConfigValue) {
+ when(mResources.getInteger(com.android.internal.R.integer.config_doubleTapPowerGestureMode))
+ .thenReturn(modeConfigValue);
}
- private void withDoubleTapPowerGestureEnableSettingValue(boolean enable) {
+ private void withMultiTargetDoubleTapPowerGestureEnableSettingValue(boolean enable) {
Settings.Secure.putIntForUser(
mContentResolver,
Settings.Secure.DOUBLE_TAP_POWER_BUTTON_GESTURE_ENABLED,
@@ -1910,8 +1932,8 @@ public class GestureLauncherServiceTest {
private void enableWalletGesture() {
withDefaultDoubleTapPowerGestureAction(LAUNCH_WALLET_ON_DOUBLE_TAP_POWER);
- withDoubleTapPowerGestureEnableSettingValue(true);
- withDoubleTapPowerEnabledConfigValue(true);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
withUserSetupCompleteValue(true);
@@ -1926,8 +1948,9 @@ public class GestureLauncherServiceTest {
private void enableCameraGesture() {
if (launchWalletOptionOnPowerDoubleTap()) {
- withDoubleTapPowerEnabledConfigValue(true);
- withDoubleTapPowerGestureEnableSettingValue(true);
+ withDoubleTapPowerModeConfigValue(
+ DOUBLE_TAP_POWER_MULTI_TARGET_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(true);
withDefaultDoubleTapPowerGestureAction(LAUNCH_CAMERA_ON_DOUBLE_TAP_POWER);
} else {
withCameraDoubleTapPowerEnableConfigValue(true);
@@ -1937,6 +1960,18 @@ public class GestureLauncherServiceTest {
withUserSetupCompleteValue(true);
}
+ private void disableDoubleTapPowerGesture() {
+ if (launchWalletOptionOnPowerDoubleTap()) {
+ withDoubleTapPowerModeConfigValue(DOUBLE_TAP_POWER_DISABLED_MODE);
+ withMultiTargetDoubleTapPowerGestureEnableSettingValue(false);
+ } else {
+ withCameraDoubleTapPowerEnableConfigValue(false);
+ withCameraDoubleTapPowerDisableSettingValue(1);
+ }
+ mGestureLauncherService.updateWalletDoubleTapPowerEnabled();
+ withUserSetupCompleteValue(true);
+ }
+
private void sendPowerKeyDownToGestureLauncherServiceAndAssertValues(
long eventTime, boolean expectedIntercept, boolean expectedOutLaunchedValue) {
KeyEvent keyEvent =
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
new file mode 100644
index 000000000000..efea21428937
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/HearingDevicePhoneCallNotificationControllerTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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.accessibility;
+
+import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Application;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.bluetooth.BluetoothAdapter;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioManager;
+import android.telephony.TelephonyCallback;
+import android.telephony.TelephonyManager;
+
+import androidx.annotation.NonNull;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for the {@link HearingDevicePhoneCallNotificationController}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class HearingDevicePhoneCallNotificationControllerTest {
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+
+ private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
+
+ private final Application mApplication = ApplicationProvider.getApplicationContext();
+ @Spy
+ private final Context mContext = mApplication.getApplicationContext();
+ private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ @Mock
+ private TelephonyManager mTelephonyManager;
+ @Mock
+ private NotificationManager mNotificationManager;
+ @Mock
+ private AudioManager mAudioManager;
+ private HearingDevicePhoneCallNotificationController mController;
+ private TestCallStateListener mTestCallStateListener;
+
+ @Before
+ public void setUp() {
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(BLUETOOTH_PRIVILEGED);
+ when(mContext.getSystemService(TelephonyManager.class)).thenReturn(mTelephonyManager);
+ when(mContext.getSystemService(NotificationManager.class)).thenReturn(mNotificationManager);
+ when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
+
+ mTestCallStateListener = new TestCallStateListener(mContext);
+ mController = new HearingDevicePhoneCallNotificationController(mContext,
+ mTestCallStateListener);
+ mController.startListenForCallState();
+ }
+
+ @Test
+ public void startListenForCallState_callbackNotNull() {
+ Mockito.reset(mTelephonyManager);
+ mController = new HearingDevicePhoneCallNotificationController(mContext);
+ ArgumentCaptor<TelephonyCallback> listenerCaptor = ArgumentCaptor.forClass(
+ TelephonyCallback.class);
+
+ mController.startListenForCallState();
+
+ verify(mTelephonyManager).registerTelephonyCallback(any(Executor.class),
+ listenerCaptor.capture());
+ TelephonyCallback callback = listenerCaptor.getValue();
+ assertThat(callback).isNotNull();
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHook_hapDevice_showNotification() {
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ verify(mNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHook_a2dpDevice_noNotification() {
+ AudioDeviceInfo a2dpDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLUETOOTH_A2DP);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{a2dpDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(a2dpDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+
+ verify(mNotificationManager, never()).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH), any());
+ }
+
+ @Test
+ public void onCallStateChanged_stateOffHookThenIdle_hapDeviceInfo_cancelNotification() {
+ AudioDeviceInfo hapDeviceInfo = createAudioDeviceInfo(TEST_ADDRESS,
+ AudioManager.DEVICE_OUT_BLE_HEADSET);
+ when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
+ new AudioDeviceInfo[]{hapDeviceInfo});
+ when(mAudioManager.getAvailableCommunicationDevices()).thenReturn(List.of(hapDeviceInfo));
+
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_OFFHOOK);
+ mTestCallStateListener.onCallStateChanged(TelephonyManager.CALL_STATE_IDLE);
+
+ verify(mNotificationManager).cancel(
+ eq(SystemMessageProto.SystemMessage.NOTE_HEARING_DEVICE_INPUT_SWITCH));
+ }
+
+ private AudioDeviceInfo createAudioDeviceInfo(String address, int type) {
+ AudioDevicePort audioDevicePort = mock(AudioDevicePort.class);
+ doReturn(type).when(audioDevicePort).type();
+ doReturn(address).when(audioDevicePort).address();
+ doReturn("testDevice").when(audioDevicePort).name();
+
+ return new AudioDeviceInfo(audioDevicePort);
+ }
+
+ /**
+ * For easier testing for CallStateListener, override methods that contain final object.
+ */
+ private static class TestCallStateListener extends
+ HearingDevicePhoneCallNotificationController.CallStateListener {
+
+ TestCallStateListener(@NonNull Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean isHapClientSupported() {
+ return true;
+ }
+
+ @Override
+ boolean isHapClientDevice(BluetoothAdapter bluetoothAdapter, AudioDeviceInfo info) {
+ return TEST_ADDRESS.equals(info.getAddress());
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/OWNERS b/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
index b74281edbf52..c824c3948e2d 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/accessibility/OWNERS
@@ -1 +1,3 @@
+# Bug component: 44215
+
include /core/java/android/view/accessibility/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
new file mode 100644
index 000000000000..84713079c9d3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpSqlPersistenceTest.java
@@ -0,0 +1,309 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_RECEIVER;
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_TRUSTED;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.os.Process;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.appop.DiscreteOpsSqlRegistry.DiscreteOp;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DiscreteAppOpSqlPersistenceTest {
+ private static final String DATABASE_NAME = "test_app_ops.db";
+ private DiscreteOpsSqlRegistry mDiscreteRegistry;
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ @Before
+ public void setUp() {
+ mDiscreteRegistry = new DiscreteOpsSqlRegistry(mContext,
+ mContext.getDatabasePath(DATABASE_NAME));
+ mDiscreteRegistry.systemReady();
+ }
+
+ @After
+ public void cleanUp() {
+ mContext.deleteDatabase(DATABASE_NAME);
+ }
+
+ @Test
+ public void discreteOpEventIsRecorded() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ List<DiscreteOp> discreteOps = mDiscreteRegistry.getCachedDiscreteOps();
+ assertThat(discreteOps.size()).isEqualTo(1);
+ assertThat(discreteOps).contains(opEvent);
+ }
+
+ @Test
+ public void discreteOpEventIsPersistedToDisk() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ flushDiscreteOpsToDatabase();
+ assertThat(mDiscreteRegistry.getCachedDiscreteOps()).isEmpty();
+ List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+ assertThat(discreteOps.size()).isEqualTo(1);
+ assertThat(discreteOps).contains(opEvent);
+ }
+
+ @Test
+ public void discreteOpEventInSameMinuteIsNotRecorded() {
+ long oneMinuteMillis = Duration.ofMinutes(1).toMillis();
+ // round timestamp at minute level and add 5 seconds
+ long accessTime = System.currentTimeMillis() / oneMinuteMillis * oneMinuteMillis + 5000;
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).setAccessTime(accessTime).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ // create duplicate event in same minute, with added 30 seconds
+ DiscreteOp opEvent2 =
+ new DiscreteOpBuilder(mContext).setAccessTime(accessTime + 30000).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+ List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+
+ assertThat(discreteOps.size()).isEqualTo(1);
+ assertThat(discreteOps).contains(opEvent);
+ }
+
+ @Test
+ public void multipleDiscreteOpEventAreRecorded() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setPackageName(
+ "test.package").build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+
+ List<DiscreteOp> discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+ assertThat(discreteOps).contains(opEvent);
+ assertThat(discreteOps).contains(opEvent2);
+ assertThat(discreteOps.size()).isEqualTo(2);
+ }
+
+ @Test
+ public void clearDiscreteOps() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ flushDiscreteOpsToDatabase();
+ DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setUid(12345).setPackageName(
+ "abc").build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+ mDiscreteRegistry.clearHistory();
+ assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty();
+ }
+
+ @Test
+ public void clearDiscreteOpsForPackage() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ flushDiscreteOpsToDatabase();
+ mDiscreteRegistry.recordDiscreteAccess(new DiscreteOpBuilder(mContext).build());
+ mDiscreteRegistry.clearHistory(Process.myUid(), mContext.getPackageName());
+
+ assertThat(mDiscreteRegistry.getAllDiscreteOps()).isEmpty();
+ }
+
+ @Test
+ public void offsetDiscreteOps() {
+ DiscreteOp opEvent = new DiscreteOpBuilder(mContext).build();
+ long event2AccessTime = System.currentTimeMillis() - 300000;
+ DiscreteOp opEvent2 = new DiscreteOpBuilder(mContext).setAccessTime(
+ event2AccessTime).build();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent);
+ flushDiscreteOpsToDatabase();
+ mDiscreteRegistry.recordDiscreteAccess(opEvent2);
+ long offset = Duration.ofMinutes(2).toMillis();
+
+ mDiscreteRegistry.offsetHistory(offset);
+
+ // adjust input for assertion
+ DiscreteOp e1 = new DiscreteOpBuilder(opEvent)
+ .setAccessTime(opEvent.getAccessTime() - offset).build();
+ DiscreteOp e2 = new DiscreteOpBuilder(opEvent2)
+ .setAccessTime(event2AccessTime - offset).build();
+
+ List<DiscreteOp> results = mDiscreteRegistry.getAllDiscreteOps();
+ assertThat(results.size()).isEqualTo(2);
+ assertThat(results).contains(e1);
+ assertThat(results).contains(e2);
+ }
+
+ @Test
+ public void completeAttributionChain() {
+ long chainId = 100;
+ DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+ .setChainId(chainId)
+ .setAttributionFlags(ATTRIBUTION_FLAG_RECEIVER | ATTRIBUTION_FLAG_TRUSTED)
+ .build();
+ DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+ .setChainId(chainId)
+ .setAttributionFlags(ATTRIBUTION_FLAG_ACCESSOR | ATTRIBUTION_FLAG_TRUSTED)
+ .build();
+ List<DiscreteOp> events = new ArrayList<>();
+ events.add(event1);
+ events.add(event2);
+
+ LongSparseArray<DiscreteOpsSqlRegistry.AttributionChain> chains =
+ mDiscreteRegistry.createAttributionChains(events, new ArraySet<>());
+
+ assertThat(chains.size()).isGreaterThan(0);
+ DiscreteOpsSqlRegistry.AttributionChain chain = chains.get(chainId);
+ assertThat(chain).isNotNull();
+ assertThat(chain.isComplete()).isTrue();
+ assertThat(chain.getStart()).isEqualTo(event1);
+ assertThat(chain.getLastVisible()).isEqualTo(event2);
+ }
+
+ @Test
+ public void addToHistoricalOps() {
+ long beginTimeMillis = System.currentTimeMillis();
+ DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+ .build();
+ DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+ .setUid(123457)
+ .build();
+ mDiscreteRegistry.recordDiscreteAccess(event1);
+ flushDiscreteOpsToDatabase();
+ mDiscreteRegistry.recordDiscreteAccess(event2);
+
+ long endTimeMillis = System.currentTimeMillis() + 500;
+ AppOpsManager.HistoricalOps results = new AppOpsManager.HistoricalOps(beginTimeMillis,
+ endTimeMillis);
+
+ mDiscreteRegistry.addFilteredDiscreteOpsToHistoricalOps(results, beginTimeMillis,
+ endTimeMillis, 0, 0, null, null, null, 0, new ArraySet<>());
+ Log.i("Manjeet", "TEST read " + results);
+ assertWithMessage("results shouldn't be empty").that(results.isEmpty()).isFalse();
+ }
+
+ @Test
+ public void dump() {
+ DiscreteOp event1 = new DiscreteOpBuilder(mContext)
+ .setAccessTime(1732221340628L)
+ .setUid(12345)
+ .build();
+ DiscreteOp event2 = new DiscreteOpBuilder(mContext)
+ .setAccessTime(1732227340628L)
+ .setUid(123457)
+ .build();
+ mDiscreteRegistry.recordDiscreteAccess(event1);
+ flushDiscreteOpsToDatabase();
+ mDiscreteRegistry.recordDiscreteAccess(event2);
+ }
+
+ /** This clears in-memory cache and push records into the database. */
+ private void flushDiscreteOpsToDatabase() {
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
+ }
+
+ /**
+ * Creates default op event for CAMERA app op with current time as access time
+ * and 1 minute duration
+ */
+ private static class DiscreteOpBuilder {
+ private int mUid;
+ private String mPackageName;
+ private String mAttributionTag;
+ private String mDeviceId;
+ private int mOpCode;
+ private int mOpFlags;
+ private int mAttributionFlags;
+ private int mUidState;
+ private long mChainId;
+ private long mAccessTime;
+ private long mDuration;
+
+ DiscreteOpBuilder(Context context) {
+ mUid = Process.myUid();
+ mPackageName = context.getPackageName();
+ mAttributionTag = null;
+ mDeviceId = String.valueOf(context.getDeviceId());
+ mOpCode = AppOpsManager.OP_CAMERA;
+ mOpFlags = AppOpsManager.OP_FLAG_SELF;
+ mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR;
+ mUidState = UID_STATE_FOREGROUND;
+ mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+ mAccessTime = System.currentTimeMillis();
+ mDuration = Duration.ofMinutes(1).toMillis();
+ }
+
+ DiscreteOpBuilder(DiscreteOp discreteOp) {
+ this.mUid = discreteOp.getUid();
+ this.mPackageName = discreteOp.getPackageName();
+ this.mAttributionTag = discreteOp.getAttributionTag();
+ this.mDeviceId = discreteOp.getDeviceId();
+ this.mOpCode = discreteOp.getOpCode();
+ this.mOpFlags = discreteOp.getOpFlags();
+ this.mAttributionFlags = discreteOp.getAttributionFlags();
+ this.mUidState = discreteOp.getUidState();
+ this.mChainId = discreteOp.getChainId();
+ this.mAccessTime = discreteOp.getAccessTime();
+ this.mDuration = discreteOp.getDuration();
+ }
+
+ public DiscreteOpBuilder setUid(int uid) {
+ this.mUid = uid;
+ return this;
+ }
+
+ public DiscreteOpBuilder setPackageName(String packageName) {
+ this.mPackageName = packageName;
+ return this;
+ }
+
+ public DiscreteOpBuilder setAttributionFlags(int attributionFlags) {
+ this.mAttributionFlags = attributionFlags;
+ return this;
+ }
+
+ public DiscreteOpBuilder setChainId(long chainId) {
+ this.mChainId = chainId;
+ return this;
+ }
+
+ public DiscreteOpBuilder setAccessTime(long accessTime) {
+ this.mAccessTime = accessTime;
+ return this;
+ }
+
+ public DiscreteOp build() {
+ return new DiscreteOp(mUid, mPackageName, mAttributionTag, mDeviceId, mOpCode, mOpFlags,
+ mAttributionFlags, mUidState, mChainId, mAccessTime, mDuration);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
index 2ff0c6288ece..ae973be17904 100644
--- a/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteAppOpXmlPersistenceTest.java
@@ -47,9 +47,12 @@ import org.junit.runner.RunWith;
import java.io.File;
import java.util.List;
+/**
+ * Test xml persistence implementation for discrete ops.
+ */
@RunWith(AndroidJUnit4.class)
-public class DiscreteAppOpPersistenceTest {
- private DiscreteRegistry mDiscreteRegistry;
+public class DiscreteAppOpXmlPersistenceTest {
+ private DiscreteOpsXmlRegistry mDiscreteRegistry;
private final Object mLock = new Object();
private File mMockDataDirectory;
private final Context mContext =
@@ -61,13 +64,13 @@ public class DiscreteAppOpPersistenceTest {
@Before
public void setUp() {
mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
- mDiscreteRegistry = new DiscreteRegistry(mLock, mMockDataDirectory);
+ mDiscreteRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
mDiscreteRegistry.systemReady();
}
@After
public void cleanUp() {
- mDiscreteRegistry.writeAndClearAccessHistory();
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
FileUtils.deleteContents(mMockDataDirectory);
}
@@ -87,14 +90,14 @@ public class DiscreteAppOpPersistenceTest {
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
uidState, accessTime, duration, attributionFlags, attributionChainId,
- DiscreteRegistry.ACCESS_TYPE_FINISH_OP);
+ DiscreteOpsXmlRegistry.ACCESS_TYPE_FINISH_OP);
// Verify in-memory object is correct
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
duration, uidState, opFlags, attributionFlags, attributionChainId);
// Write to disk and clear the in-memory object
- mDiscreteRegistry.writeAndClearAccessHistory();
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
// Verify the storage file is created and then verify its content is correct
File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory);
@@ -119,12 +122,12 @@ public class DiscreteAppOpPersistenceTest {
mDiscreteRegistry.recordDiscreteAccess(uid, packageName, deviceId, op, null, opFlags,
uidState, accessTime, duration, attributionFlags, attributionChainId,
- DiscreteRegistry.ACCESS_TYPE_START_OP);
+ DiscreteOpsXmlRegistry.ACCESS_TYPE_START_OP);
fetchDiscreteOpsAndValidate(uid, packageName, op, deviceId, null, accessTime,
duration, uidState, opFlags, attributionFlags, attributionChainId);
- mDiscreteRegistry.writeAndClearAccessHistory();
+ mDiscreteRegistry.writeAndClearOldAccessHistory();
File[] files = FileUtils.listFilesOrEmpty(mMockDataDirectory);
assertThat(files.length).isEqualTo(1);
@@ -136,30 +139,31 @@ public class DiscreteAppOpPersistenceTest {
int expectedOp, String expectedDeviceId, String expectedAttrTag,
long expectedAccessTime, long expectedAccessDuration, int expectedUidState,
int expectedOpFlags, int expectedAttrFlags, int expectedAttrChainId) {
- DiscreteRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();
+ DiscreteOpsXmlRegistry.DiscreteOps discreteOps = mDiscreteRegistry.getAllDiscreteOps();
assertThat(discreteOps.isEmpty()).isFalse();
assertThat(discreteOps.mUids.size()).isEqualTo(1);
- DiscreteRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
+ DiscreteOpsXmlRegistry.DiscreteUidOps discreteUidOps = discreteOps.mUids.get(expectedUid);
assertThat(discreteUidOps.mPackages.size()).isEqualTo(1);
- DiscreteRegistry.DiscretePackageOps discretePackageOps =
+ DiscreteOpsXmlRegistry.DiscretePackageOps discretePackageOps =
discreteUidOps.mPackages.get(expectedPackageName);
assertThat(discretePackageOps.mPackageOps.size()).isEqualTo(1);
- DiscreteRegistry.DiscreteOp discreteOp = discretePackageOps.mPackageOps.get(expectedOp);
+ DiscreteOpsXmlRegistry.DiscreteOp discreteOp =
+ discretePackageOps.mPackageOps.get(expectedOp);
assertThat(discreteOp.mDeviceAttributedOps.size()).isEqualTo(1);
- DiscreteRegistry.DiscreteDeviceOp discreteDeviceOp =
+ DiscreteOpsXmlRegistry.DiscreteDeviceOp discreteDeviceOp =
discreteOp.mDeviceAttributedOps.get(expectedDeviceId);
assertThat(discreteDeviceOp.mAttributedOps.size()).isEqualTo(1);
- List<DiscreteRegistry.DiscreteOpEvent> discreteOpEvents =
+ List<DiscreteOpsXmlRegistry.DiscreteOpEvent> discreteOpEvents =
discreteDeviceOp.mAttributedOps.get(expectedAttrTag);
assertThat(discreteOpEvents.size()).isEqualTo(1);
- DiscreteRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
+ DiscreteOpsXmlRegistry.DiscreteOpEvent discreteOpEvent = discreteOpEvents.get(0);
assertThat(discreteOpEvent.mNoteTime).isEqualTo(expectedAccessTime);
assertThat(discreteOpEvent.mNoteDuration).isEqualTo(expectedAccessDuration);
assertThat(discreteOpEvent.mUidState).isEqualTo(expectedUidState);
diff --git a/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
new file mode 100644
index 000000000000..21cc3bac3938
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appop/DiscreteOpsMigrationAndRollbackTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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.appop;
+
+import static android.app.AppOpsManager.ATTRIBUTION_FLAG_ACCESSOR;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.os.FileUtils;
+import android.os.Process;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class DiscreteOpsMigrationAndRollbackTest {
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final String DATABASE_NAME = "test_app_ops.db";
+ private static final int RECORD_COUNT = 500;
+ private final File mMockDataDirectory = mContext.getDir("mock_data", Context.MODE_PRIVATE);
+ final Object mLock = new Object();
+
+ @After
+ @Before
+ public void clean() {
+ mContext.deleteDatabase(DATABASE_NAME);
+ FileUtils.deleteContents(mMockDataDirectory);
+ }
+
+ @Test
+ public void migrateFromXmlToSqlite() {
+ // write records to xml registry
+ DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
+ xmlRegistry.systemReady();
+ for (int i = 1; i <= RECORD_COUNT; i++) {
+ DiscreteOpsSqlRegistry.DiscreteOp opEvent =
+ new DiscreteOpBuilder(mContext)
+ .setChainId(i)
+ .setUid(10000 + i) // make all records unique
+ .build();
+ xmlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(),
+ opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
+ opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
+ opEvent.getDuration(), opEvent.getAttributionFlags(),
+ (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+ }
+ xmlRegistry.writeAndClearOldAccessHistory();
+ assertThat(xmlRegistry.readLargestChainIdFromDiskLocked()).isEqualTo(RECORD_COUNT);
+ assertThat(xmlRegistry.getAllDiscreteOps().mUids.size()).isEqualTo(RECORD_COUNT);
+
+ // migration to sql registry
+ DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext,
+ mContext.getDatabasePath(DATABASE_NAME));
+ sqlRegistry.systemReady();
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToSqlite(xmlRegistry, sqlRegistry);
+ List<DiscreteOpsSqlRegistry.DiscreteOp> sqlOps = sqlRegistry.getAllDiscreteOps();
+
+ assertThat(xmlRegistry.getAllDiscreteOps().mUids).isEmpty();
+ assertThat(sqlOps.size()).isEqualTo(RECORD_COUNT);
+ assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
+ }
+
+ @Test
+ public void migrateFromSqliteToXml() {
+ // write to sql registry
+ DiscreteOpsSqlRegistry sqlRegistry = new DiscreteOpsSqlRegistry(mContext,
+ mContext.getDatabasePath(DATABASE_NAME));
+ sqlRegistry.systemReady();
+ for (int i = 1; i <= RECORD_COUNT; i++) {
+ DiscreteOpsSqlRegistry.DiscreteOp opEvent =
+ new DiscreteOpBuilder(mContext)
+ .setChainId(i)
+ .setUid(RECORD_COUNT + i) // make all records unique
+ .build();
+ sqlRegistry.recordDiscreteAccess(opEvent.getUid(), opEvent.getPackageName(),
+ opEvent.getDeviceId(), opEvent.getOpCode(), opEvent.getAttributionTag(),
+ opEvent.getOpFlags(), opEvent.getUidState(), opEvent.getAccessTime(),
+ opEvent.getDuration(), opEvent.getAttributionFlags(),
+ (int) opEvent.getChainId(), DiscreteOpsRegistry.ACCESS_TYPE_NOTE_OP);
+ }
+ sqlRegistry.writeAndClearOldAccessHistory();
+ assertThat(sqlRegistry.getAllDiscreteOps().size()).isEqualTo(RECORD_COUNT);
+ assertThat(sqlRegistry.getLargestAttributionChainId()).isEqualTo(RECORD_COUNT);
+
+ // migration to xml registry
+ DiscreteOpsXmlRegistry xmlRegistry = new DiscreteOpsXmlRegistry(mLock, mMockDataDirectory);
+ xmlRegistry.systemReady();
+ DiscreteOpsMigrationHelper.migrateDiscreteOpsToXml(sqlRegistry, xmlRegistry);
+ DiscreteOpsXmlRegistry.DiscreteOps xmlOps = xmlRegistry.getAllDiscreteOps();
+
+ assertThat(sqlRegistry.getAllDiscreteOps()).isEmpty();
+ assertThat(xmlOps.mLargestChainId).isEqualTo(RECORD_COUNT);
+ assertThat(xmlOps.mUids.size()).isEqualTo(RECORD_COUNT);
+ }
+
+ private static class DiscreteOpBuilder {
+ private int mUid;
+ private String mPackageName;
+ private String mAttributionTag;
+ private String mDeviceId;
+ private int mOpCode;
+ private int mOpFlags;
+ private int mAttributionFlags;
+ private int mUidState;
+ private int mChainId;
+ private long mAccessTime;
+ private long mDuration;
+
+ DiscreteOpBuilder(Context context) {
+ mUid = Process.myUid();
+ mPackageName = context.getPackageName();
+ mAttributionTag = null;
+ mDeviceId = VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+ mOpCode = AppOpsManager.OP_CAMERA;
+ mOpFlags = AppOpsManager.OP_FLAG_SELF;
+ mAttributionFlags = ATTRIBUTION_FLAG_ACCESSOR;
+ mUidState = UID_STATE_FOREGROUND;
+ mChainId = AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE;
+ mAccessTime = System.currentTimeMillis();
+ mDuration = Duration.ofMinutes(1).toMillis();
+ }
+
+ public DiscreteOpBuilder setUid(int uid) {
+ this.mUid = uid;
+ return this;
+ }
+
+ public DiscreteOpBuilder setChainId(int chainId) {
+ this.mChainId = chainId;
+ return this;
+ }
+
+ public DiscreteOpsSqlRegistry.DiscreteOp build() {
+ return new DiscreteOpsSqlRegistry.DiscreteOp(mUid, mPackageName, mAttributionTag,
+ mDeviceId,
+ mOpCode, mOpFlags, mAttributionFlags, mUidState, mChainId, mAccessTime,
+ mDuration);
+ }
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 32578a7dc10f..bdbb495db841 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -340,8 +340,7 @@ public class VirtualDeviceManagerServiceTest {
LocalServices.removeServiceForTest(DisplayManagerInternal.class);
LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
- doNothing().when(mInputManagerInternalMock)
- .setMousePointerAccelerationEnabled(anyBoolean(), anyInt());
+ doNothing().when(mInputManagerInternalMock).setMouseScalingEnabled(anyBoolean(), anyInt());
doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt());
LocalServices.removeServiceForTest(InputManagerInternal.class);
LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index 1352adef783f..ad6e467efeef 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -76,12 +76,10 @@ class OverlayReferenceMapperTests {
val overlay1 = mockOverlay(1)
mapper = mapper(
overlayToTargetToOverlayables = mapOf(
- overlay0.packageName to mapOf(
- target.packageName to target.overlayables.keys
- ),
- overlay1.packageName to mapOf(
- target.packageName to target.overlayables.keys
- )
+ overlay0.packageName to android.util.Pair(target.packageName,
+ target.overlayables.keys.first()),
+ overlay1.packageName to android.util.Pair(target.packageName,
+ target.overlayables.keys.first())
)
)
val existing = mapper.addInOrder(overlay0, overlay1) {
@@ -134,42 +132,38 @@ class OverlayReferenceMapperTests {
}
@Test
- fun overlayWithMultipleTargets() {
- val target0 = mockTarget(0)
- val target1 = mockTarget(1)
+ fun overlayWithoutTarget() {
val overlay = mockOverlay()
- mapper = mapper(
- overlayToTargetToOverlayables = mapOf(
- overlay.packageName to mapOf(
- target0.packageName to target0.overlayables.keys,
- target1.packageName to target1.overlayables.keys
- )
- )
- )
- mapper.addInOrder(target0, target1, overlay) {
- assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
- }
- assertMapping(ACTOR_PACKAGE_NAME to setOf(target0, target1, overlay))
- mapper.remove(target0) {
- assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ mapper.addInOrder(overlay) {
+ assertThat(it).isEmpty()
}
- assertMapping(ACTOR_PACKAGE_NAME to setOf(target1, overlay))
- mapper.remove(target1) {
- assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ // An overlay can only have visibility exposed through its target
+ assertEmpty()
+ mapper.remove(overlay) {
+ assertThat(it).isEmpty()
}
assertEmpty()
}
@Test
- fun overlayWithoutTarget() {
+ fun targetWithNullOverlayable() {
+ val target = mockTarget()
val overlay = mockOverlay()
- mapper.addInOrder(overlay) {
+ mapper = mapper(
+ overlayToTargetToOverlayables = mapOf(
+ overlay.packageName to android.util.Pair(target.packageName, null)
+ )
+ )
+ val existing = mapper.addInOrder(overlay) {
assertThat(it).isEmpty()
}
- // An overlay can only have visibility exposed through its target
assertEmpty()
- mapper.remove(overlay) {
- assertThat(it).isEmpty()
+ mapper.addInOrder(target, existing = existing) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
+ }
+ assertMapping(ACTOR_PACKAGE_NAME to setOf(target))
+ mapper.remove(target) {
+ assertThat(it).containsExactly(ACTOR_PACKAGE_NAME)
}
assertEmpty()
}
@@ -219,17 +213,15 @@ class OverlayReferenceMapperTests {
namedActors: Map<String, Map<String, String>> = Uri.parse(ACTOR_NAME).run {
mapOf(authority!! to mapOf(pathSegments.first() to ACTOR_PACKAGE_NAME))
},
- overlayToTargetToOverlayables: Map<String, Map<String, Set<String>>> = mapOf(
- mockOverlay().packageName to mapOf(
- mockTarget().run { packageName to overlayables.keys }
- )
- )
+ overlayToTargetToOverlayables: Map<String, android.util.Pair<String, String>> = mapOf(
+ mockOverlay().packageName to mockTarget().run { android.util.Pair(packageName!!,
+ overlayables.keys.first()) })
) = OverlayReferenceMapper(deferRebuild, object : OverlayReferenceMapper.Provider {
override fun getActorPkg(actor: String) =
OverlayActorEnforcer.getPackageNameForActor(actor, namedActors).first
override fun getTargetToOverlayables(pkg: AndroidPackage) =
- overlayToTargetToOverlayables[pkg.packageName] ?: emptyMap()
+ overlayToTargetToOverlayables[pkg.packageName]
})
private fun mockTarget(increment: Int = 0) = mockThrowOnUnmocked<AndroidPackage> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 4e030d499c25..3ef360a752f6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -112,6 +112,7 @@ import org.mockito.stubbing.Answer;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -556,6 +557,12 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase {
}
@Override
+ void injectFinishWrite(@NonNull ResilientAtomicFile file,
+ @NonNull FileOutputStream os) throws IOException {
+ file.finishWrite(os, false /* doFsVerity */);
+ }
+
+ @Override
void wtf(String message, Throwable th) {
// During tests, WTF is fatal.
fail(message + " exception: " + th + "\n" + Log.getStackTraceString(th));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index c01283a236c4..0d86d4c3fa28 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -159,7 +159,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Test for the first launch path, no settings file available.
*/
- public void FirstInitialize() {
+ public void testFirstInitialize() {
assertResetTimes(START_TIME, START_TIME + INTERVAL);
}
@@ -167,7 +167,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
* Test for {@link ShortcutService#getLastResetTimeLocked()} and
* {@link ShortcutService#getNextResetTimeLocked()}.
*/
- public void UpdateAndGetNextResetTimeLocked() {
+ public void testUpdateAndGetNextResetTimeLocked() {
assertResetTimes(START_TIME, START_TIME + INTERVAL);
// Advance clock.
@@ -196,7 +196,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Test for the restoration from saved file.
*/
- public void InitializeFromSavedFile() {
+ public void testInitializeFromSavedFile() {
mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
assertResetTimes(START_TIME + 4 * INTERVAL, START_TIME + 5 * INTERVAL);
@@ -220,7 +220,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// TODO Add various broken cases.
}
- public void LoadConfig() {
+ public void testLoadConfig() {
mService.updateConfigurationLocked(
ConfigConstants.KEY_RESET_INTERVAL_SEC + "=123,"
+ ConfigConstants.KEY_MAX_SHORTCUTS + "=4,"
@@ -261,22 +261,22 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// === Test for app side APIs ===
/** Test for {@link android.content.pm.ShortcutManager#getMaxShortcutCountForActivity()} */
- public void GetMaxDynamicShortcutCount() {
+ public void testGetMaxDynamicShortcutCount() {
assertEquals(MAX_SHORTCUTS, mManager.getMaxShortcutCountForActivity());
}
/** Test for {@link android.content.pm.ShortcutManager#getRemainingCallCount()} */
- public void GetRemainingCallCount() {
+ public void testGetRemainingCallCount() {
assertEquals(MAX_UPDATES_PER_INTERVAL, mManager.getRemainingCallCount());
}
- public void GetIconMaxDimensions() {
+ public void testGetIconMaxDimensions() {
assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxWidth());
assertEquals(MAX_ICON_DIMENSION, mManager.getIconMaxHeight());
}
/** Test for {@link android.content.pm.ShortcutManager#getRateLimitResetTime()} */
- public void GetRateLimitResetTime() {
+ public void testGetRateLimitResetTime() {
assertEquals(START_TIME + INTERVAL, mManager.getRateLimitResetTime());
mInjectedCurrentTimeMillis = START_TIME + 4 * INTERVAL + 50;
@@ -284,7 +284,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(START_TIME + 5 * INTERVAL, mManager.getRateLimitResetTime());
}
- public void SetDynamicShortcuts() {
+ public void testSetDynamicShortcuts() {
setCaller(CALLING_PACKAGE_1, USER_10);
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1);
@@ -354,7 +354,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void AddDynamicShortcuts() {
+ public void testAddDynamicShortcuts() {
setCaller(CALLING_PACKAGE_1, USER_10);
final ShortcutInfo si1 = makeShortcut("shortcut1");
@@ -402,7 +402,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PushDynamicShortcut() {
+ public void testPushDynamicShortcut() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
@@ -544,7 +544,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_10));
}
- public void PushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+ public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
throws InterruptedException {
mService.updateConfigurationLocked(
ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
@@ -576,6 +576,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
Mockito.reset(mMockUsageStatsManagerInternal);
for (int i = 2; i <= 10; i++) {
final ShortcutInfo si = makeShortcut("s" + i);
+ setCaller(CALLING_PACKAGE_2, USER_10);
mManager.pushDynamicShortcut(si);
}
verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
@@ -595,7 +596,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
eq(CALLING_PACKAGE_2), any(), eq(USER_10));
}
- public void UnlimitedCalls() {
+ public void testUnlimitedCalls() {
setCaller(CALLING_PACKAGE_1, USER_10);
final ShortcutInfo si1 = makeShortcut("shortcut1");
@@ -626,7 +627,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(3, mManager.getRemainingCallCount());
}
- public void PublishWithNoActivity() {
+ public void testPublishWithNoActivity() {
// If activity is not explicitly set, use the default one.
mRunningUsers.put(USER_11, true);
@@ -732,7 +733,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PublishWithNoActivity_noMainActivityInPackage() {
+ public void testPublishWithNoActivity_noMainActivityInPackage() {
mRunningUsers.put(USER_11, true);
runWithCaller(CALLING_PACKAGE_2, USER_11, () -> {
@@ -751,7 +752,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void DeleteDynamicShortcuts() {
+ public void testDeleteDynamicShortcuts() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
final ShortcutInfo si2 = makeShortcut("shortcut2");
final ShortcutInfo si3 = makeShortcut("shortcut3");
@@ -792,7 +793,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(2, mManager.getRemainingCallCount());
}
- public void DeleteAllDynamicShortcuts() {
+ public void testDeleteAllDynamicShortcuts() {
final ShortcutInfo si1 = makeShortcut("shortcut1");
final ShortcutInfo si2 = makeShortcut("shortcut2");
final ShortcutInfo si3 = makeShortcut("shortcut3");
@@ -821,7 +822,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(1, mManager.getRemainingCallCount());
}
- public void Icons() throws IOException {
+ public void testIcons() throws IOException {
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
final Icon res64x64 = Icon.createWithResource(getTestContext(), R.drawable.black_64x64);
final Icon res512x512 = Icon.createWithResource(getTestContext(), R.drawable.black_512x512);
@@ -1035,7 +1036,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
*/
}
- public void CleanupDanglingBitmaps() throws Exception {
+ public void testCleanupDanglingBitmaps() throws Exception {
assertBitmapDirectories(USER_10, EMPTY_STRINGS);
assertBitmapDirectories(USER_11, EMPTY_STRINGS);
@@ -1204,7 +1205,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
maxSize));
}
- public void ShrinkBitmap() {
+ public void testShrinkBitmap() {
checkShrinkBitmap(32, 32, R.drawable.black_512x512, 32);
checkShrinkBitmap(511, 511, R.drawable.black_512x512, 511);
checkShrinkBitmap(512, 512, R.drawable.black_512x512, 512);
@@ -1227,7 +1228,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
return out.getFile();
}
- public void OpenIconFileForWrite() throws IOException {
+ public void testOpenIconFileForWrite() throws IOException {
mInjectedCurrentTimeMillis = 1000;
final File p10_1_1 = openIconFileForWriteAndGetPath(10, CALLING_PACKAGE_1);
@@ -1301,7 +1302,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertFalse(p11_1_3.getName().contains("_"));
}
- public void UpdateShortcuts() {
+ public void testUpdateShortcuts() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
@@ -1432,7 +1433,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void UpdateShortcuts_icons() {
+ public void testUpdateShortcuts_icons() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1")
@@ -1526,7 +1527,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ShortcutManagerGetShortcuts_shortcutTypes() {
+ public void testShortcutManagerGetShortcuts_shortcutTypes() {
// Create 3 manifest and 3 dynamic shortcuts
addManifestShortcutResource(
@@ -1617,7 +1618,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s1", "s2");
}
- public void CachedShortcuts() {
+ public void testCachedShortcuts() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
@@ -1701,7 +1702,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
"s2");
}
- public void CachedShortcuts_accessShortcutsPermission() {
+ public void testCachedShortcuts_accessShortcutsPermission() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"),
makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"),
@@ -1743,7 +1744,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED), "s3");
}
- public void CachedShortcuts_canPassShortcutLimit() {
+ public void testCachedShortcuts_canPassShortcutLimit() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=4");
@@ -1781,7 +1782,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// === Test for launcher side APIs ===
- public void GetShortcuts() {
+ public void testGetShortcuts() {
// Set up shortcuts.
@@ -1998,7 +1999,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
"s1", "s3");
}
- public void GetShortcuts_shortcutKinds() throws Exception {
+ public void testGetShortcuts_shortcutKinds() throws Exception {
// Create 3 manifest and 3 dynamic shortcuts
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -2109,7 +2110,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void GetShortcuts_resolveStrings() throws Exception {
+ public void testGetShortcuts_resolveStrings() throws Exception {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
ShortcutInfo si = new ShortcutInfo.Builder(mClientContext)
.setId("id")
@@ -2157,7 +2158,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void GetShortcuts_personsFlag() {
+ public void testGetShortcuts_personsFlag() {
ShortcutInfo s = new ShortcutInfo.Builder(mClientContext, "id")
.setShortLabel("label")
.setActivity(new ComponentName(mClientContext, ShortcutActivity2.class))
@@ -2205,7 +2206,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
// TODO resource
- public void GetShortcutInfo() {
+ public void testGetShortcutInfo() {
// Create shortcuts.
setCaller(CALLING_PACKAGE_1);
final ShortcutInfo s1_1 = makeShortcut(
@@ -2280,7 +2281,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals("ABC", findById(list, "s1").getTitle());
}
- public void PinShortcutAndGetPinnedShortcuts() {
+ public void testPinShortcutAndGetPinnedShortcuts() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2361,7 +2362,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
* This is similar to the above test, except it used "disable" instead of "remove". It also
* does "enable".
*/
- public void DisableAndEnableShortcuts() {
+ public void testDisableAndEnableShortcuts() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final ShortcutInfo s1_1 = makeShortcutWithTimestamp("s1", 1000);
final ShortcutInfo s1_2 = makeShortcutWithTimestamp("s2", 2000);
@@ -2486,7 +2487,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void DisableShortcuts_thenRepublish() {
+ public void testDisableShortcuts_thenRepublish() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"), makeShortcut("s2"), makeShortcut("s3"))));
@@ -2556,7 +2557,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PinShortcutAndGetPinnedShortcuts_multi() {
+ public void testPinShortcutAndGetPinnedShortcuts_multi() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -2832,7 +2833,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PinShortcutAndGetPinnedShortcuts_assistant() {
+ public void testPinShortcutAndGetPinnedShortcuts_assistant() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -2888,7 +2889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
+ public void testPinShortcutAndGetPinnedShortcuts_crossProfile_plusLaunch() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -3477,7 +3478,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void StartShortcut() {
+ public void testStartShortcut() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final ShortcutInfo s1_1 = makeShortcut(
@@ -3612,7 +3613,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// TODO Check extra, etc
}
- public void LauncherCallback() throws Throwable {
+ public void testLauncherCallback() throws Throwable {
// Disable throttling for this test.
mService.updateConfigurationLocked(
ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL + "=99999999,"
@@ -3778,7 +3779,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.isEmpty();
}
- public void LauncherCallback_crossProfile() throws Throwable {
+ public void testLauncherCallback_crossProfile() throws Throwable {
prepareCrossProfileDataSet();
final Handler h = new Handler(Looper.getMainLooper());
@@ -3901,7 +3902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// === Test for persisting ===
- public void SaveAndLoadUser_empty() {
+ public void testSaveAndLoadUser_empty() {
assertTrue(mManager.setDynamicShortcuts(list()));
Log.i(TAG, "Saved state");
@@ -3918,7 +3919,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Try save and load, also stop/start the user.
*/
- public void SaveAndLoadUser() {
+ public void testSaveAndLoadUser() {
// First, create some shortcuts and save.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -4059,7 +4060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// TODO Check all other fields
}
- public void LoadCorruptedShortcuts() throws Exception {
+ public void testLoadCorruptedShortcuts() throws Exception {
initService();
addPackage("com.android.chrome", 0, 0);
@@ -4073,7 +4074,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertNull(ShortcutPackage.loadFromFile(mService, user, corruptedShortcutPackage, false));
}
- public void SaveCorruptAndLoadUser() throws Exception {
+ public void testSaveCorruptAndLoadUser() throws Exception {
// First, create some shortcuts and save.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.black_64x16);
@@ -4229,7 +4230,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
// TODO Check all other fields
}
- public void CleanupPackage() {
+ public void testCleanupPackage() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s0_1"))));
@@ -4506,7 +4507,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.saveDirtyInfo();
}
- public void CleanupPackage_republishManifests() {
+ public void testCleanupPackage_republishManifests() {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_2);
@@ -4574,7 +4575,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HandleGonePackage_crossProfile() {
+ public void testHandleGonePackage_crossProfile() {
// Create some shortcuts.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
@@ -4846,7 +4847,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertEquals(expected, spi.canRestoreTo(mService, pi, true));
}
- public void CanRestoreTo() {
+ public void testCanRestoreTo() {
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sig1");
addPackage(CALLING_PACKAGE_2, CALLING_UID_2, 10, "sig1", "sig2");
addPackage(CALLING_PACKAGE_3, CALLING_UID_3, 10, "sig1");
@@ -4909,7 +4910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkCanRestoreTo(DISABLED_REASON_BACKUP_NOT_SUPPORTED, spi3, true, 10, true, "sig1");
}
- public void HandlePackageDelete() {
+ public void testHandlePackageDelete() {
checkHandlePackageDeleteInner((userId, packageName) -> {
uninstallPackage(userId, packageName);
mService.mPackageMonitor.onReceive(getTestContext(),
@@ -4917,7 +4918,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HandlePackageDisable() {
+ public void testHandlePackageDisable() {
checkHandlePackageDeleteInner((userId, packageName) -> {
disablePackage(userId, packageName);
mService.mPackageMonitor.onReceive(getTestContext(),
@@ -5049,7 +5050,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
/** Almost ame as testHandlePackageDelete, except it doesn't uninstall packages. */
- public void HandlePackageClearData() {
+ public void testHandlePackageClearData() {
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
setCaller(CALLING_PACKAGE_1, USER_10);
@@ -5125,7 +5126,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertTrue(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_11));
}
- public void HandlePackageClearData_manifestRepublished() {
+ public void testHandlePackageClearData_manifestRepublished() {
mRunningUsers.put(USER_11, true);
@@ -5167,7 +5168,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HandlePackageUpdate() throws Throwable {
+ public void testHandlePackageUpdate() throws Throwable {
// Set up shortcuts and launchers.
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
@@ -5341,7 +5342,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Test the case where an updated app has resource IDs changed.
*/
- public void HandlePackageUpdate_resIdChanged() throws Exception {
+ public void testHandlePackageUpdate_resIdChanged() throws Exception {
final Icon icon1 = Icon.createWithResource(getTestContext(), /* res ID */ 1000);
final Icon icon2 = Icon.createWithResource(getTestContext(), /* res ID */ 1001);
@@ -5416,7 +5417,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HandlePackageUpdate_systemAppUpdate() {
+ public void testHandlePackageUpdate_systemAppUpdate() {
// Package1 is a system app. Package 2 is not a system app, so it's not scanned
// in this test at all.
@@ -5522,7 +5523,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mService.getUserShortcutsLocked(USER_10).getLastAppScanOsFingerprint());
}
- public void HandlePackageChanged() {
+ public void testHandlePackageChanged() {
final ComponentName ACTIVITY1 = new ComponentName(CALLING_PACKAGE_1, "act1");
final ComponentName ACTIVITY2 = new ComponentName(CALLING_PACKAGE_1, "act2");
@@ -5652,7 +5653,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HandlePackageUpdate_activityNoLongerMain() throws Throwable {
+ public void testHandlePackageUpdate_activityNoLongerMain() throws Throwable {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithActivity("s1a",
@@ -5738,7 +5739,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
* - Unpinned dynamic shortcuts
* - Bitmaps
*/
- public void BackupAndRestore() {
+ public void testBackupAndRestore() {
assertFileNotExists("user-0/shortcut_dump/restore-0-start.txt");
assertFileNotExists("user-0/shortcut_dump/restore-1-payload.xml");
@@ -5759,7 +5760,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_success(/*firstRestore=*/ true);
}
- public void BackupAndRestore_backupRestoreTwice() {
+ public void testBackupAndRestore_backupRestoreTwice() {
prepareForBackupTest();
checkBackupAndRestore_success(/*firstRestore=*/ true);
@@ -5775,7 +5776,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_success(/*firstRestore=*/ false);
}
- public void BackupAndRestore_restoreToNewVersion() {
+ public void testBackupAndRestore_restoreToNewVersion() {
prepareForBackupTest();
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 2);
@@ -5784,7 +5785,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_success(/*firstRestore=*/ true);
}
- public void BackupAndRestore_restoreToSuperSetSignatures() {
+ public void testBackupAndRestore_restoreToSuperSetSignatures() {
prepareForBackupTest();
// Change package signatures.
@@ -5981,7 +5982,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void BackupAndRestore_publisherWrongSignature() {
+ public void testBackupAndRestore_publisherWrongSignature() {
prepareForBackupTest();
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "sigx"); // different signature
@@ -5989,7 +5990,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_publisherNotRestored(ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH);
}
- public void BackupAndRestore_publisherNoLongerBackupTarget() {
+ public void testBackupAndRestore_publisherNoLongerBackupTarget() {
prepareForBackupTest();
updatePackageInfo(CALLING_PACKAGE_1,
@@ -6118,7 +6119,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void BackupAndRestore_launcherLowerVersion() {
+ public void testBackupAndRestore_launcherLowerVersion() {
prepareForBackupTest();
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 0); // Lower version
@@ -6127,7 +6128,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_success(/*firstRestore=*/ true);
}
- public void BackupAndRestore_launcherWrongSignature() {
+ public void testBackupAndRestore_launcherWrongSignature() {
prepareForBackupTest();
addPackage(LAUNCHER_1, LAUNCHER_UID_1, 10, "sigx"); // different signature
@@ -6135,7 +6136,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
checkBackupAndRestore_launcherNotRestored(true);
}
- public void BackupAndRestore_launcherNoLongerBackupTarget() {
+ public void testBackupAndRestore_launcherNoLongerBackupTarget() {
prepareForBackupTest();
updatePackageInfo(LAUNCHER_1,
@@ -6240,7 +6241,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void BackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
+ public void testBackupAndRestore_launcherAndPackageNoLongerBackupTarget() {
prepareForBackupTest();
updatePackageInfo(CALLING_PACKAGE_1,
@@ -6338,7 +6339,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void BackupAndRestore_disabled() {
+ public void testBackupAndRestore_disabled() {
prepareCrossProfileDataSet();
// Before doing backup & restore, disable s1.
@@ -6403,7 +6404,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
- public void BackupAndRestore_manifestRePublished() {
+ public void testBackupAndRestore_manifestRePublished() {
// Publish two manifest shortcuts.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -6494,7 +6495,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
* logcat.
* - if it has allowBackup=false, we don't touch any of the existing shortcuts.
*/
- public void BackupAndRestore_appAlreadyInstalledWhenRestored() {
+ public void testBackupAndRestore_appAlreadyInstalledWhenRestored() {
// Pre-backup. Same as testBackupAndRestore_manifestRePublished().
// Publish two manifest shortcuts.
@@ -6619,7 +6620,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Test for restoring the pre-P backup format.
*/
- public void BackupAndRestore_api27format() throws Exception {
+ public void testBackupAndRestore_api27format() throws Exception {
final byte[] payload = readTestAsset("shortcut/shortcut_api27_backup.xml").getBytes();
addPackage(CALLING_PACKAGE_1, CALLING_UID_1, 10, "22222");
@@ -6657,7 +6658,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
- public void SaveAndLoad_crossProfile() {
+ public void testSaveAndLoad_crossProfile() {
prepareCrossProfileDataSet();
dumpsysOnLogcat("Before save & load");
@@ -6860,7 +6861,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
.getPackageUserId());
}
- public void OnApplicationActive_permission() {
+ public void testOnApplicationActive_permission() {
assertExpectException(SecurityException.class, "Missing permission", () ->
mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10));
@@ -6869,7 +6870,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mManager.onApplicationActive(CALLING_PACKAGE_1, USER_10);
}
- public void GetShareTargets_permission() {
+ public void testGetShareTargets_permission() {
addPackage(CHOOSER_ACTIVITY_PACKAGE, CHOOSER_ACTIVITY_UID, 10, "sig1");
mInjectedChooserActivity =
ComponentName.createRelative(CHOOSER_ACTIVITY_PACKAGE, ".ChooserActivity");
@@ -6888,7 +6889,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void HasShareTargets_permission() {
+ public void testHasShareTargets_permission() {
assertExpectException(SecurityException.class, "Missing permission", () ->
mManager.hasShareTargets(CALLING_PACKAGE_1));
@@ -6897,7 +6898,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mManager.hasShareTargets(CALLING_PACKAGE_1);
}
- public void isSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException {
+ public void testisSharingShortcut_permission() throws IntentFilter.MalformedMimeTypeException {
setCaller(LAUNCHER_1, USER_10);
IntentFilter filter_any = new IntentFilter();
@@ -6912,18 +6913,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
mManager.hasShareTargets(CALLING_PACKAGE_1);
}
- public void Dumpsys_crossProfile() {
+ public void testDumpsys_crossProfile() {
prepareCrossProfileDataSet();
dumpsysOnLogcat("test1", /* force= */ true);
}
- public void Dumpsys_withIcons() throws IOException {
- Icons();
+ public void testDumpsys_withIcons() throws IOException {
+ testIcons();
// Dump after having some icons.
dumpsysOnLogcat("test1", /* force= */ true);
}
- public void ManifestShortcut_publishOnUnlockUser() {
+ public void testManifestShortcut_publishOnUnlockUser() {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_1);
@@ -7137,7 +7138,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertNull(mService.getPackageShortcutForTest(LAUNCHER_1, USER_10));
}
- public void ManifestShortcut_publishOnBroadcast() {
+ public void testManifestShortcut_publishOnBroadcast() {
// First, no packages are installed.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
uninstallPackage(USER_10, CALLING_PACKAGE_2);
@@ -7393,7 +7394,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_missingMandatoryFields() {
+ public void testManifestShortcuts_missingMandatoryFields() {
// Start with no apps installed.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
uninstallPackage(USER_10, CALLING_PACKAGE_2);
@@ -7462,7 +7463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_intentDefinitions() {
+ public void testManifestShortcuts_intentDefinitions() {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_error_4);
@@ -7604,7 +7605,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_checkAllFields() {
+ public void testManifestShortcuts_checkAllFields() {
mService.handleUnlockUser(USER_10);
// Package 1 updated, which has one valid manifest shortcut and one invalid.
@@ -7709,7 +7710,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_localeChange() throws InterruptedException {
+ public void testManifestShortcuts_localeChange() throws InterruptedException {
mService.handleUnlockUser(USER_10);
// Package 1 updated, which has one valid manifest shortcut and one invalid.
@@ -7813,7 +7814,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_updateAndDisabled_notPinned() {
+ public void testManifestShortcuts_updateAndDisabled_notPinned() {
mService.handleUnlockUser(USER_10);
// First, just publish a manifest shortcut.
@@ -7853,7 +7854,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_updateAndDisabled_pinned() {
+ public void testManifestShortcuts_updateAndDisabled_pinned() {
mService.handleUnlockUser(USER_10);
// First, just publish a manifest shortcut.
@@ -7909,7 +7910,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_duplicateInSingleActivity() {
+ public void testManifestShortcuts_duplicateInSingleActivity() {
mService.handleUnlockUser(USER_10);
// The XML has two shortcuts with the same ID.
@@ -7934,7 +7935,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ManifestShortcuts_duplicateInTwoActivities() {
+ public void testManifestShortcuts_duplicateInTwoActivities() {
mService.handleUnlockUser(USER_10);
// ShortcutActivity has shortcut ms1
@@ -7986,7 +7987,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Manifest shortcuts cannot override shortcuts that were published via the APIs.
*/
- public void ManifestShortcuts_cannotOverrideNonManifest() {
+ public void testManifestShortcuts_cannotOverrideNonManifest() {
mService.handleUnlockUser(USER_10);
// Create a non-pinned dynamic shortcut and a non-dynamic pinned shortcut.
@@ -8059,7 +8060,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Make sure the APIs won't work on manifest shortcuts.
*/
- public void ManifestShortcuts_immutable() {
+ public void testManifestShortcuts_immutable() {
mService.handleUnlockUser(USER_10);
// Create a non-pinned manifest shortcut, a pinned shortcut that was originally
@@ -8152,7 +8153,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
/**
* Make sure the APIs won't work on manifest shortcuts.
*/
- public void ManifestShortcuts_tooMany() {
+ public void testManifestShortcuts_tooMany() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
@@ -8171,7 +8172,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void MaxShortcutCount_set() {
+ public void testMaxShortcutCount_set() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
@@ -8252,7 +8253,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void MaxShortcutCount_add() {
+ public void testMaxShortcutCount_add() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
@@ -8379,7 +8380,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void MaxShortcutCount_update() {
+ public void testMaxShortcutCount_update() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
@@ -8470,7 +8471,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ShortcutsPushedOutByManifest() {
+ public void testShortcutsPushedOutByManifest() {
// Change the max number of shortcuts.
mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=3");
@@ -8578,7 +8579,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void ReturnedByServer() {
+ public void testReturnedByServer() {
// Package 1 updated, with manifest shortcuts.
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -8624,7 +8625,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void IsForegroundDefaultLauncher_true() {
+ public void testIsForegroundDefaultLauncher_true() {
// random uid in the USER_10 range.
final int uid = 1000024;
@@ -8635,7 +8636,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
- public void IsForegroundDefaultLauncher_defaultButNotForeground() {
+ public void testIsForegroundDefaultLauncher_defaultButNotForeground() {
// random uid in the USER_10 range.
final int uid = 1000024;
@@ -8645,7 +8646,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertFalse(mInternal.isForegroundDefaultLauncher("default", uid));
}
- public void IsForegroundDefaultLauncher_foregroundButNotDefault() {
+ public void testIsForegroundDefaultLauncher_foregroundButNotDefault() {
// random uid in the USER_10 range.
final int uid = 1000024;
@@ -8655,7 +8656,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
assertFalse(mInternal.isForegroundDefaultLauncher("another", uid));
}
- public void ParseShareTargetsFromManifest() {
+ public void testParseShareTargetsFromManifest() {
// These values must exactly match the content of shortcuts_share_targets.xml resource
List<ShareTargetInfo> expectedValues = new ArrayList<>();
expectedValues.add(new ShareTargetInfo(
@@ -8707,7 +8708,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
}
- public void ShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
+ public void testShareTargetInfo_saveToXml() throws IOException, XmlPullParserException {
List<ShareTargetInfo> expectedValues = new ArrayList<>();
expectedValues.add(new ShareTargetInfo(
new ShareTargetInfo.TargetData[]{new ShareTargetInfo.TargetData(
@@ -8773,7 +8774,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
}
}
- public void IsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
+ public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
R.xml.shortcut_share_targets);
@@ -8823,7 +8824,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
filter_any));
}
- public void IsSharingShortcut_PinnedAndCachedOnlyShortcuts()
+ public void testIsSharingShortcut_PinnedAndCachedOnlyShortcuts()
throws IntentFilter.MalformedMimeTypeException {
addManifestShortcutResource(
new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
@@ -8880,7 +8881,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
filter_any));
}
- public void AddingShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+ public void testAddingShortcuts_ExcludesHiddenFromLauncherShortcuts() {
final ShortcutInfo s1 = makeShortcutExcludedFromLauncher("s1");
final ShortcutInfo s2 = makeShortcutExcludedFromLauncher("s2");
final ShortcutInfo s3 = makeShortcutExcludedFromLauncher("s3");
@@ -8901,7 +8902,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void UpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
+ public void testUpdateShortcuts_ExcludesHiddenFromLauncherShortcuts() {
final ShortcutInfo s1 = makeShortcut("s1");
final ShortcutInfo s2 = makeShortcut("s2");
final ShortcutInfo s3 = makeShortcut("s3");
@@ -8914,7 +8915,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
});
}
- public void PinHiddenShortcuts_ThrowsException() {
+ public void testPinHiddenShortcuts_ThrowsException() {
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertThrown(IllegalArgumentException.class, () -> {
mManager.requestPinShortcut(makeShortcutExcludedFromLauncher("s1"), null);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index af7f703e9c31..b33233107766 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -101,9 +101,12 @@ public class ConditionProvidersTest extends UiServiceTestCase {
mProviders.notifyConditions("package", msi, conditionsToNotify);
- verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
- verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
- verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]),
+ eq(100));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]),
+ eq(100));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("c")), eq(conditionsToNotify[2]),
+ eq(100));
verifyNoMoreInteractions(mCallback);
}
@@ -121,8 +124,10 @@ public class ConditionProvidersTest extends UiServiceTestCase {
mProviders.notifyConditions("package", msi, conditionsToNotify);
- verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
- verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]),
+ eq(100));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[1]),
+ eq(100));
verifyNoMoreInteractions(mCallback);
}
@@ -141,8 +146,10 @@ public class ConditionProvidersTest extends UiServiceTestCase {
mProviders.notifyConditions("package", msi, conditionsToNotify);
- verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]));
- verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("a")), eq(conditionsToNotify[0]),
+ eq(100));
+ verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]),
+ eq(100));
verifyNoMoreInteractions(mCallback);
}
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 301165f8151d..7885c9b902e2 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -11213,7 +11213,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Representative used to verify getCallingZenUser().
mBinderService.getAutomaticZenRules();
- verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT));
+ verify(zenModeHelper).getAutomaticZenRules(eq(UserHandle.CURRENT), anyInt());
}
@Test
@@ -11225,7 +11225,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// Representative used to verify getCallingZenUser().
mBinderService.getAutomaticZenRules();
- verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle()));
+ verify(zenModeHelper).getAutomaticZenRules(eq(Binder.getCallingUserHandle()), anyInt());
}
/** Prepares for a zen-related test that uses a mocked {@link ZenModeHelper}. */
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 1884bbd39bb9..6ef078b6da8a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -291,7 +291,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING);
+ return FlagsParameterization.allCombinationsOf(FLAG_MODES_UI, FLAG_BACKUP_RESTORE_LOGGING,
+ com.android.server.notification.Flags.FLAG_FIX_CALLING_UID_FROM_CPS);
}
public ZenModeHelperTest(FlagsParameterization flags) {
@@ -2617,7 +2618,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
- public void testSetAutomaticZenRuleState_nullPkg() {
+ public void testSetAutomaticZenRuleStateFromConditionProvider_nullPkg() {
AutomaticZenRule zenRule = new AutomaticZenRule("name",
null,
new ComponentName(mContext.getPackageName(), "ScheduleConditionProvider"),
@@ -2627,10 +2628,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String id = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT, null, zenRule,
ORIGIN_APP, "test", CUSTOM_PKG_UID);
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, zenRule.getConditionId(),
- new Condition(zenRule.getConditionId(), "", STATE_TRUE),
- ORIGIN_APP,
- CUSTOM_PKG_UID);
+ mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT,
+ zenRule.getConditionId(), new Condition(zenRule.getConditionId(), "", STATE_TRUE),
+ ORIGIN_APP, CUSTOM_PKG_UID);
ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id);
assertEquals(STATE_TRUE, ruleInConfig.condition.state);
@@ -2726,8 +2726,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_SYSTEM, "test", SYSTEM_UID);
Condition condition = new Condition(sharedUri, "", STATE_TRUE);
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
- ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, sharedUri,
+ condition, ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2741,8 +2741,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
condition = new Condition(sharedUri, "", STATE_FALSE);
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, sharedUri, condition,
- ORIGIN_SYSTEM, SYSTEM_UID);
+ mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT, sharedUri,
+ condition, ORIGIN_SYSTEM, SYSTEM_UID);
for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) {
if (rule.id.equals(id)) {
@@ -2780,9 +2780,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -2814,9 +2815,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(zde)
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2845,7 +2847,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_USER_IN_SYSTEMUI,
"reasons", 0);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@@ -2863,7 +2866,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2875,9 +2878,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(updateFromApp)
.build(),
- ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(
new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // From update.
@@ -2898,7 +2902,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true) // Good
@@ -2908,9 +2912,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromSystem)
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem);
}
@@ -2926,7 +2931,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setOwner(OWNER)
.setDeviceEffects(original)
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder()
.setShouldUseNightMode(true)
@@ -2939,9 +2944,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
.setDeviceEffects(updateFromUser)
.build(),
- ORIGIN_USER_IN_SYSTEMUI, "reasons", 0);
+ ORIGIN_USER_IN_SYSTEMUI, "reasons", SYSTEM_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
}
@@ -2959,15 +2965,16 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowCalls(ZenPolicy.PEOPLE_TYPE_NONE) // default is stars
.build())
.build(),
- ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
// no zen policy
.build(),
- ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_DISALLOW);
}
@@ -2988,7 +2995,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowReminders(true)
.build())
.build(),
- ORIGIN_SYSTEM, "reasons", 0);
+ ORIGIN_SYSTEM, "reasons", SYSTEM_UID);
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder("Rule", CONDITION_ID)
@@ -2996,9 +3003,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowCalls(ZenPolicy.PEOPLE_TYPE_CONTACTS)
.build())
.build(),
- ORIGIN_APP, "reasons", 0);
+ ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
- AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
assertThat(savedRule.getZenPolicy().getPriorityCategoryCalls())
.isEqualTo(STATE_ALLOW); // from update
assertThat(savedRule.getZenPolicy().getPriorityCallSenders())
@@ -4441,7 +4449,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.triggerDescription = TRIGGER_DESC;
mZenModeHelper.mConfig.automaticRules.put(rule.id, rule);
- AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id);
+ AutomaticZenRule actual = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, rule.id,
+ SYSTEM_UID);
assertEquals(NAME, actual.getName());
assertEquals(OWNER, actual.getOwner());
@@ -4508,16 +4517,17 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// Checks the name can be changed by the app because the user has not modified it.
AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setName("NewName")
.build();
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
- "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ "reason", CUSTOM_PKG_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
assertThat(rule.getName()).isEqualTo("NewName");
// The user modifies some other field in the rule, which makes the rule as a whole not
@@ -4534,8 +4544,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setName("NewAppName")
.build();
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
- "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ "reason", CUSTOM_PKG_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
assertThat(rule.getName()).isEqualTo("NewAppName");
// The user modifies the name.
@@ -4544,7 +4554,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
// The app is no longer able to modify the name.
@@ -4552,8 +4562,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setName("NewAppName")
.build();
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
- "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ "reason", CUSTOM_PKG_UID);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
}
@@ -4568,8 +4578,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// Modifies the filter, icon, zen policy, and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4589,7 +4600,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Update the rule with the AZR from origin user.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -4625,8 +4636,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// Modifies the icon, zen policy and device effects
ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
@@ -4646,7 +4658,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Update the rule with the AZR from origin systemUI.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_SYSTEM,
"reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
assertThat(rule.getIconResId()).isEqualTo(ICON_RES_ID);
@@ -4675,8 +4687,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
ZenPolicy policy = new ZenPolicy.Builder()
.allowReminders(true)
@@ -4693,7 +4706,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azrUpdate, ORIGIN_APP,
- "reason", SYSTEM_UID);
+ "reason", CUSTOM_PKG_UID);
ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(storedRule.userModifiedFields).isEqualTo(0);
@@ -4717,9 +4730,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleIdUser, azrUpdate, ORIGIN_APP,
- "reason", SYSTEM_UID);
+ "reason", CUSTOM_PKG_UID);
AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- ruleIdUser);
+ ruleIdUser, CUSTOM_PKG_UID);
// The app can only change the value if the rule is not already user modified,
// so the rule is not changed, and neither is the bitmask.
@@ -4749,8 +4762,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build())
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// The values are modified but the bitmask is not.
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
@@ -4771,7 +4785,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Sets Device Effects to null
@@ -4781,8 +4795,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
- SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// When AZR's ZenDeviceEffects is null, the updated rule's device effects are kept.
assertThat(rule.getDeviceEffects()).isEqualTo(zde);
@@ -4797,8 +4812,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Set zen policy to null
@@ -4808,8 +4822,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr, ORIGIN_APP, "reason",
- SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// When AZR's ZenPolicy is null, we expect the updated rule's policy to be unchanged
// (equivalent to the provided policy, with additional fields filled in with defaults).
@@ -4829,8 +4844,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
// Create a fully populated ZenPolicy.
ZenPolicy policy = new ZenPolicy.Builder()
@@ -4860,7 +4874,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Default config defined in getDefaultConfigParser() is used as the original rule.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
@@ -4890,8 +4905,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
// Adds the rule using the app, to avoid having any user modified bits set.
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
- mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ mContext.getPackageName(), azrBase, ORIGIN_APP, "reason", CUSTOM_PKG_UID);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -4903,7 +4919,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Applies the update to the rule.
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, azr,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID);
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
assertThat(rule.getDeviceEffects()).isNotNull();
@@ -5286,7 +5302,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("brand.new.cps");
}
@@ -5306,7 +5323,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId, update,
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
- AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule result = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID);
assertThat(result).isNotNull();
assertThat(result.getOwner().getClassName()).isEqualTo("old.third.party.cps");
}
@@ -5518,8 +5536,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
- .isEqualTo(1000);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000);
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
@@ -5546,7 +5564,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// - ZenPolicy is the one that the user had set.
// - rule still has the user-modified fields.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getCreationTime()).isEqualTo(1000); // And not 3000.
assertThat(newRuleId).isEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
@@ -5575,8 +5593,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
- .isEqualTo(1000);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000);
// App deletes it.
mTestClock.advanceByMillis(1000);
@@ -5592,7 +5610,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Verify that the rule was recreated. This means id and creation time are new.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getCreationTime()).isEqualTo(3000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5609,8 +5627,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
- .isEqualTo(1000);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID)
+ .getCreationTime()).isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5637,7 +5655,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Verify that the rule was recreated. This means id and creation time are new, and the rule
// matches the latest data supplied to addAZR.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
@@ -5660,8 +5678,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
String ruleId = mZenModeHelper.addAutomaticZenRule(UserHandle.CURRENT,
mContext.getPackageName(), rule, ORIGIN_APP, "add it", CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId).getCreationTime())
- .isEqualTo(1000);
+ assertThat(mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ CUSTOM_PKG_UID).getCreationTime()).isEqualTo(1000);
// User customizes it.
mTestClock.advanceByMillis(1000);
@@ -5686,7 +5704,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Verify that the rule was recreated. This means id and creation time are new.
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getCreationTime()).isEqualTo(4000);
assertThat(newRuleId).isNotEqualTo(ruleId);
}
@@ -5728,7 +5746,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Verify that the rule was NOT restored:
assertThat(newRuleId).isNotEqualTo(ruleId);
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
assertThat(finalRule.getOwner()).isEqualTo(new ComponentName("second", "owner"));
@@ -5869,7 +5887,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NOT active
@@ -5923,7 +5941,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// The rule is restored...
assertThat(newRuleId).isEqualTo(ruleId);
AutomaticZenRule finalRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- newRuleId);
+ newRuleId, CUSTOM_PKG_UID);
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
// ... but it is NEITHER active NOR snoozed.
@@ -6005,22 +6023,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_APP, "reasons", CUSTOM_PKG_UID);
// Null condition -> STATE_FALSE
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID))
.isEqualTo(Condition.STATE_FALSE);
mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_TRUE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID))
.isEqualTo(Condition.STATE_TRUE);
mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, id, CONDITION_FALSE, ORIGIN_APP,
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID))
.isEqualTo(Condition.STATE_FALSE);
mZenModeHelper.removeAutomaticZenRule(UserHandle.CURRENT, id, ORIGIN_APP, "",
CUSTOM_PKG_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id))
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, id, CUSTOM_PKG_UID))
.isEqualTo(Condition.STATE_UNKNOWN);
}
@@ -6036,8 +6054,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.setConfig(config, null, ORIGIN_INIT, "", SYSTEM_UID);
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule"))
- .isEqualTo(Condition.STATE_UNKNOWN);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, "systemRule",
+ CUSTOM_PKG_UID)).isEqualTo(Condition.STATE_UNKNOWN);
}
@Test
@@ -6063,7 +6081,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(FLAG_MODES_API)
- public void setAutomaticZenRuleState_conditionForNotOwnedRule_ignored() {
+ public void setAutomaticZenRuleStateFromConditionProvider_conditionForNotOwnedRule_ignored() {
// Assume existence of an other-package-owned rule that is currently ACTIVE.
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_OFF);
ZenRule otherRule = newZenRule("another.package", Instant.now(), null);
@@ -6075,7 +6093,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
// Should be ignored.
- mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, otherRule.conditionId,
+ mZenModeHelper.setAutomaticZenRuleStateFromConditionProvider(UserHandle.CURRENT,
+ otherRule.conditionId,
new Condition(otherRule.conditionId, "off", Condition.STATE_FALSE),
ORIGIN_APP, CUSTOM_PKG_UID);
@@ -6182,7 +6201,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update that rule's interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.build();
@@ -6214,7 +6234,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
// From user, update something in that rule, but not the interruption filter.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Renamed")
.build();
@@ -6315,7 +6336,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
// User chooses a new name.
- AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
@@ -6414,7 +6436,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update that rule's policy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
.allowAlarms(true).build();
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
@@ -6456,7 +6479,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.mConfig.getZenPolicy()).allowMedia(true).build();
// From user, update something in that rule, but not the ZenPolicy.
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
.setName("Rule renamed, not touching policy")
.build();
@@ -6509,7 +6533,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = ZenModeConfig.implicitRuleId(mContext.getPackageName());
// User chooses a new name.
- AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId);
+ AutomaticZenRule azr = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID);
mZenModeHelper.updateAutomaticZenRule(UserHandle.CURRENT, ruleId,
new AutomaticZenRule.Builder(azr).setName("User chose this").build(),
ORIGIN_USER_IN_SYSTEMUI, "reason", SYSTEM_UID);
@@ -6645,7 +6670,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(),
ORIGIN_APP, "reason", CUSTOM_PKG_UID);
AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(UserHandle.CURRENT,
- ruleId);
+ ruleId, CUSTOM_PKG_UID);
assertThat(storedRule.getIconResId()).isEqualTo(0);
}
@@ -7087,8 +7112,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id))
- .isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id,
+ CUSTOM_PKG_UID)).isEqualTo(STATE_TRUE);
assertThat(implicitRule.isActive()).isTrue();
assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
}
@@ -7108,8 +7133,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
implicitRule = getZenRule(implicitRuleId(CUSTOM_PKG_NAME));
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id))
- .isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, implicitRule.id,
+ CUSTOM_PKG_UID)).isEqualTo(STATE_FALSE);
assertThat(implicitRule.isActive()).isFalse();
assertThat(implicitRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
}
@@ -7177,7 +7202,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "manual-on", STATE_TRUE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, SYSTEM_UID))
.isEqualTo(STATE_TRUE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
@@ -7192,14 +7217,14 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
if (Flags.modesUi()) {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
- .isEqualTo(STATE_TRUE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID)).isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_ACTIVATE);
assertThat(zenRule.condition).isNull();
} else {
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
- .isEqualTo(STATE_FALSE);
+ assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
+ SYSTEM_UID)).isEqualTo(STATE_FALSE);
}
}
@@ -7218,7 +7243,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.setAutomaticZenRuleState(UserHandle.CURRENT, ruleId,
new Condition(rule.getConditionId(), "snooze", STATE_FALSE, SOURCE_USER_ACTION),
ORIGIN_USER_IN_SYSTEMUI, SYSTEM_UID);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ assertThat(
+ mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID))
.isEqualTo(STATE_FALSE);
ZenRule zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_DEACTIVATE);
@@ -7232,7 +7258,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL, null);
- assertThat(mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId))
+ assertThat(
+ mZenModeHelper.getAutomaticZenRuleState(UserHandle.CURRENT, ruleId, CUSTOM_PKG_UID))
.isEqualTo(STATE_TRUE);
zenRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
assertThat(zenRule.getConditionOverride()).isEqualTo(OVERRIDE_NONE);
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index 9d4d94bebfd9..85ef466b2477 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -758,6 +758,18 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
}
@Test
+ @EnableFlags(com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES)
+ public void testKeyGestureToggleVoiceAccess() {
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(true);
+
+ Assert.assertTrue(
+ sendKeyGestureEventComplete(KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS));
+ mPhoneWindowManager.assertVoiceAccess(false);
+ }
+
+ @Test
public void testKeyGestureToggleDoNotDisturb() {
mPhoneWindowManager.overrideZenMode(Settings.Global.ZEN_MODE_OFF);
Assert.assertTrue(
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6c48ba26a475..4ff3d433632a 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -201,6 +201,8 @@ class TestPhoneWindowManager {
private boolean mIsTalkBackEnabled;
private boolean mIsTalkBackShortcutGestureEnabled;
+ private boolean mIsVoiceAccessEnabled;
+
private Intent mBrowserIntent;
private Intent mSmsIntent;
@@ -225,6 +227,18 @@ class TestPhoneWindowManager {
}
}
+ private class TestVoiceAccessShortcutController extends VoiceAccessShortcutController {
+ TestVoiceAccessShortcutController(Context context) {
+ super(context);
+ }
+
+ @Override
+ boolean toggleVoiceAccess(int currentUserId) {
+ mIsVoiceAccessEnabled = !mIsVoiceAccessEnabled;
+ return mIsVoiceAccessEnabled;
+ }
+ }
+
private class TestInjector extends PhoneWindowManager.Injector {
TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
super(context, funcs);
@@ -260,6 +274,10 @@ class TestPhoneWindowManager {
return new TestTalkbackShortcutController(mContext);
}
+ VoiceAccessShortcutController getVoiceAccessShortcutController() {
+ return new TestVoiceAccessShortcutController(mContext);
+ }
+
WindowWakeUpPolicy getWindowWakeUpPolicy() {
return mWindowWakeUpPolicy;
}
@@ -1024,6 +1042,11 @@ class TestPhoneWindowManager {
Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
}
+ void assertVoiceAccess(boolean expectEnabled) {
+ mTestLooper.dispatchAll();
+ Assert.assertEquals(expectEnabled, mIsVoiceAccessEnabled);
+ }
+
void assertKeyGestureEventSentToKeyGestureController(int gestureType) {
verify(mInputManagerInternal)
.handleKeyGestureInKeyGestureController(anyInt(), any(), anyInt(), eq(gestureType));
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index c9cbe0fa08c5..6fad82b26808 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -210,7 +210,7 @@ public class ActivityRecordTests extends WindowTestsBase {
}
private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
- return new TestStartingWindowOrganizer(mAtm);
+ return new TestStartingWindowOrganizer(mAtm, mDisplayContent);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
index b8d554b405d1..98a4fb3c473f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatResizeOverridesTest.java
@@ -184,12 +184,12 @@ public class AppCompatResizeOverridesTest extends WindowTestsBase {
void checkShouldOverrideForceResizeApp(boolean expected) {
Assert.assertEquals(expected, activity().top().mAppCompatController
- .getAppCompatResizeOverrides().shouldOverrideForceResizeApp());
+ .getResizeOverrides().shouldOverrideForceResizeApp());
}
void checkShouldOverrideForceNonResizeApp(boolean expected) {
Assert.assertEquals(expected, activity().top().mAppCompatController
- .getAppCompatResizeOverrides().shouldOverrideForceNonResizeApp());
+ .getResizeOverrides().shouldOverrideForceNonResizeApp());
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index b6e393d7be0c..03d904283e83 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -342,8 +342,8 @@ public class AppTransitionTests extends WindowTestsBase {
public void testCancelRemoteAnimationWhenFreeze() {
final DisplayContent dc = createNewDisplay(Display.STATE_ON);
doReturn(false).when(dc).onDescendantOrientationChanged(any());
- final WindowState exitingAppWindow = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- dc, "exiting app");
+ final WindowState exitingAppWindow = newWindowBuilder("exiting app",
+ TYPE_BASE_APPLICATION).setDisplay(dc).build();
final ActivityRecord exitingActivity = exitingAppWindow.mActivityRecord;
// Wait until everything in animation handler get executed to prevent the exiting window
// from being removed during WindowSurfacePlacer Traversal.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
index 14276ae21899..7033d79d0ee1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java
@@ -266,10 +266,10 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase {
mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH);
prepareSecondaryDisplay();
- final WindowState defaultDisplayWindow = createWindow(/* parent= */ null,
- TYPE_BASE_APPLICATION, mDisplayContent, "DefaultDisplayWindow");
- final WindowState secondaryDisplayWindow = createWindow(/* parent= */ null,
- TYPE_BASE_APPLICATION, mSecondaryDisplayContent, "SecondaryDisplayWindow");
+ final WindowState defaultDisplayWindow = newWindowBuilder("DefaultDisplayWindow",
+ TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
+ final WindowState secondaryDisplayWindow = newWindowBuilder("SecondaryDisplayWindow",
+ TYPE_BASE_APPLICATION).setDisplay(mSecondaryDisplayContent).build();
makeWindowVisibleAndNotDrawn(defaultDisplayWindow, secondaryDisplayWindow);
// Mark as display switching only for the default display as we filter out
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index ea925c019b77..4854f0d948b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -88,7 +88,7 @@ public class DisplayPolicyTests extends WindowTestsBase {
}
private WindowState createDreamWindow() {
- final WindowState win = createDreamWindow(null, TYPE_BASE_APPLICATION, "dream");
+ final WindowState win = createDreamWindow("dream", TYPE_BASE_APPLICATION);
final WindowManager.LayoutParams attrs = win.mAttrs;
attrs.width = MATCH_PARENT;
attrs.height = MATCH_PARENT;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index bd15bc42e811..347d1bc1becc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -379,13 +379,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
- final WindowState firstActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
- "firstActivityWin");
+ final WindowState firstActivityWin = newWindowBuilder("firstActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build();
spyOn(firstActivityWin);
- final WindowState secondActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
- "firstActivityWin");
+ final WindowState secondActivityWin = newWindowBuilder("secondActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mSecondActivity).build();
spyOn(secondActivityWin);
// firstActivityWin should be the target
@@ -424,13 +422,11 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
setupImeWindow();
final DisplayArea.Tokens imeContainer = mDisplay.getImeContainer();
final WindowToken imeToken = tokenOfType(TYPE_INPUT_METHOD);
- final WindowState firstActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
- "firstActivityWin");
+ final WindowState firstActivityWin = newWindowBuilder("firstActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build();
spyOn(firstActivityWin);
- final WindowState secondActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mSecondActivity,
- "secondActivityWin");
+ final WindowState secondActivityWin = newWindowBuilder("secondActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mSecondActivity).build();
spyOn(secondActivityWin);
// firstActivityWin should be the target
@@ -464,9 +460,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
assertThat(mDisplay.findAreaForTokenInLayer(imeToken)).isEqualTo(imeContainer);
- final WindowState firstActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
- "firstActivityWin");
+ final WindowState firstActivityWin = newWindowBuilder("firstActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build();
spyOn(firstActivityWin);
// firstActivityWin should be the target
doReturn(true).when(firstActivityWin).canBeImeTarget();
@@ -499,9 +494,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
assertThat(imeContainer.getRootDisplayArea()).isEqualTo(mDisplay);
// firstActivityWin should be the target
- final WindowState firstActivityWin =
- createWindow(null /* parent */, TYPE_APPLICATION_STARTING, mFirstActivity,
- "firstActivityWin");
+ final WindowState firstActivityWin = newWindowBuilder("firstActivityWin",
+ TYPE_APPLICATION_STARTING).setWindowToken(mFirstActivity).build();
spyOn(firstActivityWin);
doReturn(true).when(firstActivityWin).canBeImeTarget();
WindowState imeTarget = mDisplay.computeImeTarget(true /* updateImeTarget */);
@@ -560,8 +554,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
}
private void setupImeWindow() {
- final WindowState imeWindow = createWindow(null /* parent */,
- TYPE_INPUT_METHOD, mDisplay, "mImeWindow");
+ final WindowState imeWindow = newWindowBuilder("mImeWindow", TYPE_INPUT_METHOD).setDisplay(
+ mDisplay).build();
imeWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
mDisplay.mInputMethodWindow = imeWindow;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index dc4adcc4315b..299717393028 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -878,8 +878,10 @@ public class TaskFragmentTest extends WindowTestsBase {
.build();
final ActivityRecord activity0 = tf0.getTopMostActivity();
final ActivityRecord activity1 = tf1.getTopMostActivity();
- final WindowState win0 = createWindow(null, TYPE_BASE_APPLICATION, activity0, "win0");
- final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity1, "win1");
+ final WindowState win0 = newWindowBuilder("win0", TYPE_BASE_APPLICATION).setWindowToken(
+ activity0).build();
+ final WindowState win1 = newWindowBuilder("win1", TYPE_BASE_APPLICATION).setWindowToken(
+ activity1).build();
doReturn(false).when(mDisplayContent).shouldImeAttachedToApp();
mDisplayContent.setImeInputTarget(win0);
@@ -1174,8 +1176,8 @@ public class TaskFragmentTest extends WindowTestsBase {
}
private WindowState createAppWindow(ActivityRecord app, String name) {
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, app, name,
- 0 /* ownerId */, false /* ownerCanAddInternalSystemWindow */, new TestIWindow());
+ final WindowState win = newWindowBuilder(name, TYPE_BASE_APPLICATION).setWindowToken(
+ app).setClientWindow(new TestIWindow()).build();
mWm.mWindowMap.put(win.mClient.asBinder(), win);
return win;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index f145b40d2292..f9250f9ecf5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -63,7 +63,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testAppRemoved() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
mCache.onAppRemoved(window.mActivityRecord);
@@ -72,7 +72,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testAppDied() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
mCache.onAppDied(window.mActivityRecord);
@@ -81,7 +81,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testTaskRemoved() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mCache.putSnapshot(window.getTask(), createSnapshot());
assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
mCache.onIdRemoved(window.getTask().mTaskId);
@@ -90,7 +90,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testReduced_notCached() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
@@ -105,7 +105,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testRestoreFromDisk() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
@@ -117,7 +117,7 @@ public class TaskSnapshotCacheTest extends TaskSnapshotPersisterTestBase {
@Test
public void testClearCache() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mCache.putSnapshot(window.getTask(), mSnapshot);
assertEquals(mSnapshot, mCache.getSnapshot(window.getTask().mTaskId,
false /* isLowResolution */));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index c6b2a6b8d42f..1bca53aff034 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -74,8 +74,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
@Test
public void testGetClosingApps_closing() {
- final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
- "closingWindow");
+ final WindowState closingWindow = newWindowBuilder("closingWindow",
+ FIRST_APPLICATION_WINDOW).build();
closingWindow.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
@@ -88,8 +88,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
@Test
public void testGetClosingApps_notClosing() {
- final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
- "closingWindow");
+ final WindowState closingWindow = newWindowBuilder("closingWindow",
+ FIRST_APPLICATION_WINDOW).build();
final WindowState openingWindow = createAppWindow(closingWindow.getTask(),
FIRST_APPLICATION_WINDOW, "openingWindow");
closingWindow.mActivityRecord.commitVisibility(
@@ -105,8 +105,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
@Test
public void testGetClosingApps_skipClosingAppsSnapshotTasks() {
- final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
- "closingWindow");
+ final WindowState closingWindow = newWindowBuilder("closingWindow",
+ FIRST_APPLICATION_WINDOW).build();
closingWindow.mActivityRecord.commitVisibility(
false /* visible */, true /* performLayout */);
final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
@@ -133,19 +133,19 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
@Test
public void testGetSnapshotMode() {
- final WindowState disabledWindow = createWindow(null,
- FIRST_APPLICATION_WINDOW, mDisplayContent, "disabledWindow");
+ final WindowState disabledWindow = newWindowBuilder("disabledWindow",
+ FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build();
disabledWindow.mActivityRecord.setRecentsScreenshotEnabled(false);
assertEquals(SNAPSHOT_MODE_APP_THEME,
mWm.mTaskSnapshotController.getSnapshotMode(disabledWindow.getTask()));
- final WindowState normalWindow = createWindow(null,
- FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow");
+ final WindowState normalWindow = newWindowBuilder("normalWindow",
+ FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build();
assertEquals(SNAPSHOT_MODE_REAL,
mWm.mTaskSnapshotController.getSnapshotMode(normalWindow.getTask()));
- final WindowState secureWindow = createWindow(null,
- FIRST_APPLICATION_WINDOW, mDisplayContent, "secureWindow");
+ final WindowState secureWindow = newWindowBuilder("secureWindow",
+ FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build();
secureWindow.mAttrs.flags |= FLAG_SECURE;
assertEquals(SNAPSHOT_MODE_APP_THEME,
mWm.mTaskSnapshotController.getSnapshotMode(secureWindow.getTask()));
@@ -297,8 +297,8 @@ public class TaskSnapshotControllerTest extends WindowTestsBase {
spyOn(mWm.mTaskSnapshotController);
doReturn(false).when(mWm.mTaskSnapshotController).shouldDisableSnapshots();
- final WindowState normalWindow = createWindow(null,
- FIRST_APPLICATION_WINDOW, mDisplayContent, "normalWindow");
+ final WindowState normalWindow = newWindowBuilder("normalWindow",
+ FIRST_APPLICATION_WINDOW).setDisplay(mDisplayContent).build();
final TaskSnapshot snapshot = new TaskSnapshotPersisterTestBase.TaskSnapshotBuilder()
.setTopActivityComponent(normalWindow.mActivityRecord.mActivityComponent).build();
doReturn(snapshot).when(mWm.mTaskSnapshotController).snapshot(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
index 9bde0663d4a3..51ea498811fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
@@ -41,7 +41,7 @@ import java.io.File;
* Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader}
*
* Build/Install/Run:
- * atest TaskSnapshotPersisterLoaderTest
+ * atest TaskSnapshotLowResDisabledTest
*/
@MediumTest
@Presubmit
@@ -126,7 +126,7 @@ public class TaskSnapshotLowResDisabledTest extends TaskSnapshotPersisterTestBas
@Test
public void testReduced_notCached() {
- final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
+ final WindowState window = newWindowBuilder("window", FIRST_APPLICATION_WINDOW).build();
mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
mSnapshotPersistQueue.waitForQueueEmpty();
assertNull(mCache.getSnapshot(window.getTask().mTaskId, false /* isLowResolution */));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 1fa657822189..5ed2df30518b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -504,8 +504,8 @@ public class WindowContainerTests extends WindowTestsBase {
assertTrue(child.isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION));
assertFalse(child.isAnimating(PARENTS, ANIMATION_TYPE_SCREEN_ROTATION));
- final WindowState windowState = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- mDisplayContent, "TestWindowState");
+ final WindowState windowState = newWindowBuilder("TestWindowState",
+ TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
WindowContainer parent = windowState.getParent();
spyOn(windowState.mSurfaceAnimator);
doReturn(true).when(windowState.mSurfaceAnimator).isAnimating();
@@ -1045,8 +1045,8 @@ public class WindowContainerTests extends WindowTestsBase {
// An animating window with mRemoveOnExit can be removed by handleCompleteDeferredRemoval
// once it no longer animates.
- final WindowState exitingWindow = createWindow(null, TYPE_APPLICATION_OVERLAY,
- displayContent, "exiting window");
+ final WindowState exitingWindow = newWindowBuilder("exiting window",
+ TYPE_APPLICATION_OVERLAY).setDisplay(displayContent).build();
exitingWindow.startAnimation(exitingWindow.getPendingTransaction(),
mock(AnimationAdapter.class), false /* hidden */,
SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
@@ -1063,7 +1063,7 @@ public class WindowContainerTests extends WindowTestsBase {
final ActivityRecord r = new TaskBuilder(mSupervisor).setCreateActivity(true)
.setDisplay(displayContent).build().getTopMostActivity();
// Add a window and make the activity animating so the removal of activity is deferred.
- createWindow(null, TYPE_BASE_APPLICATION, r, "win");
+ newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(r).build();
doReturn(true).when(r).isAnimating(anyInt(), anyInt());
displayContent.remove();
@@ -1216,7 +1216,8 @@ public class WindowContainerTests extends WindowTestsBase {
public void testFreezeInsets() {
final Task task = createTask(mDisplayContent);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
+ activity).build();
// Set visibility to false, verify the main window of the task will be set the frozen
// insets state immediately.
@@ -1233,7 +1234,8 @@ public class WindowContainerTests extends WindowTestsBase {
final Task rootTask = createTask(mDisplayContent);
final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
- final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_BASE_APPLICATION).setWindowToken(
+ activity).build();
task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
spyOn(win);
doReturn(true).when(task).okToAnimate();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 72935cb546d9..8606581539ff 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -49,9 +49,10 @@ public class WindowContainerTraversalTests extends WindowTestsBase {
@SetupWindows(addWindows = { W_DOCK_DIVIDER, W_INPUT_METHOD })
@Test
public void testDockedDividerPosition() {
- final WindowState splitScreenWindow = createWindow(null,
- WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
- mDisplayContent, "splitScreenWindow");
+ final WindowState splitScreenWindow = newWindowBuilder("splitScreenWindow",
+ TYPE_BASE_APPLICATION).setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay(
+ mDisplayContent).build();
mDisplayContent.setImeLayeringTarget(splitScreenWindow);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 50e0e181cd68..ab9abfc4a876 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -154,9 +154,11 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsParentWindowHidden() {
- final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
- final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
- final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
+ final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build();
+ final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
+ final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
// parentWindow is initially set to hidden.
assertTrue(parentWindow.mHidden);
@@ -172,10 +174,12 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsChildWindow() {
- final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
- final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
- final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
- final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow");
+ final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build();
+ final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
+ final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
+ final WindowState randomWindow = newWindowBuilder("randomWindow", TYPE_APPLICATION).build();
assertFalse(parentWindow.isChildWindow());
assertTrue(child1.isChildWindow());
@@ -185,12 +189,15 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testHasChild() {
- final WindowState win1 = createWindow(null, TYPE_APPLICATION, "win1");
- final WindowState win11 = createWindow(win1, FIRST_SUB_WINDOW, "win11");
- final WindowState win12 = createWindow(win1, FIRST_SUB_WINDOW, "win12");
- final WindowState win2 = createWindow(null, TYPE_APPLICATION, "win2");
- final WindowState win21 = createWindow(win2, FIRST_SUB_WINDOW, "win21");
- final WindowState randomWindow = createWindow(null, TYPE_APPLICATION, "randomWindow");
+ final WindowState win1 = newWindowBuilder("win1", TYPE_APPLICATION).build();
+ final WindowState win11 = newWindowBuilder("win11", FIRST_SUB_WINDOW).setParent(
+ win1).build();
+ final WindowState win12 = newWindowBuilder("win12", FIRST_SUB_WINDOW).setParent(
+ win1).build();
+ final WindowState win2 = newWindowBuilder("win2", TYPE_APPLICATION).build();
+ final WindowState win21 = newWindowBuilder("win21", FIRST_SUB_WINDOW).setParent(
+ win2).build();
+ final WindowState randomWindow = newWindowBuilder("randomWindow", TYPE_APPLICATION).build();
assertTrue(win1.hasChild(win11));
assertTrue(win1.hasChild(win12));
@@ -206,9 +213,11 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testGetParentWindow() {
- final WindowState parentWindow = createWindow(null, TYPE_APPLICATION, "parentWindow");
- final WindowState child1 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child1");
- final WindowState child2 = createWindow(parentWindow, FIRST_SUB_WINDOW, "child2");
+ final WindowState parentWindow = newWindowBuilder("parentWindow", TYPE_APPLICATION).build();
+ final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
+ final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent(
+ parentWindow).build();
assertNull(parentWindow.getParentWindow());
assertEquals(parentWindow, child1.getParentWindow());
@@ -217,8 +226,8 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testOverlayWindowHiddenWhenSuspended() {
- final WindowState overlayWindow = spy(createWindow(null, TYPE_APPLICATION_OVERLAY,
- "overlayWindow"));
+ final WindowState overlayWindow = spy(
+ newWindowBuilder("overlayWindow", TYPE_APPLICATION_OVERLAY).build());
overlayWindow.setHiddenWhileSuspended(true);
verify(overlayWindow).hide(true /* doAnimation */, true /* requestAnim */);
overlayWindow.setHiddenWhileSuspended(false);
@@ -227,9 +236,11 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testGetTopParentWindow() {
- final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
- final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
- final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
+ final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build();
+ final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent(
+ root).build();
+ final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent(
+ child1).build();
assertEquals(root, root.getTopParentWindow());
assertEquals(root, child1.getTopParentWindow());
@@ -244,7 +255,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsOnScreen_hiddenByPolicy() {
- final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+ final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build();
window.setHasSurface(true);
assertTrue(window.isOnScreen());
window.hide(false /* doAnimation */, false /* requestAnim */);
@@ -273,8 +284,8 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCanBeImeTarget() {
- final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
- final WindowState imeWindow = createWindow(null, TYPE_INPUT_METHOD, "imeWindow");
+ final WindowState appWindow = newWindowBuilder("appWindow", TYPE_APPLICATION).build();
+ final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_INPUT_METHOD).build();
// Setting FLAG_NOT_FOCUSABLE prevents the window from being an IME target.
appWindow.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
@@ -328,16 +339,17 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testGetWindow() {
- final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
- final WindowState mediaChild = createWindow(root, TYPE_APPLICATION_MEDIA, "mediaChild");
- final WindowState mediaOverlayChild = createWindow(root,
- TYPE_APPLICATION_MEDIA_OVERLAY, "mediaOverlayChild");
- final WindowState attachedDialogChild = createWindow(root,
- TYPE_APPLICATION_ATTACHED_DIALOG, "attachedDialogChild");
- final WindowState subPanelChild = createWindow(root,
- TYPE_APPLICATION_SUB_PANEL, "subPanelChild");
- final WindowState aboveSubPanelChild = createWindow(root,
- TYPE_APPLICATION_ABOVE_SUB_PANEL, "aboveSubPanelChild");
+ final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build();
+ final WindowState mediaChild = newWindowBuilder("mediaChild",
+ TYPE_APPLICATION_MEDIA).setParent(root).build();
+ final WindowState mediaOverlayChild = newWindowBuilder("mediaOverlayChild",
+ TYPE_APPLICATION_MEDIA_OVERLAY).setParent(root).build();
+ final WindowState attachedDialogChild = newWindowBuilder("attachedDialogChild",
+ TYPE_APPLICATION_ATTACHED_DIALOG).setParent(root).build();
+ final WindowState subPanelChild = newWindowBuilder("subPanelChild",
+ TYPE_APPLICATION_SUB_PANEL).setParent(root).build();
+ final WindowState aboveSubPanelChild = newWindowBuilder("aboveSubPanelChild",
+ TYPE_APPLICATION_ABOVE_SUB_PANEL).setParent(root).build();
final LinkedList<WindowState> windows = new LinkedList<>();
@@ -358,7 +370,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testDestroySurface() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build();
win.mHasSurface = win.mAnimatingExit = true;
win.mWinAnimator.mSurfaceControl = mock(SurfaceControl.class);
win.onExitAnimationDone();
@@ -384,8 +396,10 @@ public class WindowStateTests extends WindowTestsBase {
// Call prepareWindowToDisplayDuringRelayout for a window without FLAG_TURN_SCREEN_ON before
// calling setCurrentLaunchCanTurnScreenOn for windows with flag in the same activity.
final ActivityRecord activity = createActivityRecord(mDisplayContent);
- final WindowState first = createWindow(null, TYPE_APPLICATION, activity, "first");
- final WindowState second = createWindow(null, TYPE_APPLICATION, activity, "second");
+ final WindowState first = newWindowBuilder("first", TYPE_APPLICATION).setWindowToken(
+ activity).build();
+ final WindowState second = newWindowBuilder("second", TYPE_APPLICATION).setWindowToken(
+ activity).build();
testPrepareWindowToDisplayDuringRelayout(first, false /* expectedWakeupCalled */,
true /* expectedCurrentLaunchCanTurnScreenOn */);
@@ -423,10 +437,10 @@ public class WindowStateTests extends WindowTestsBase {
// Call prepareWindowToDisplayDuringRelayout for a windows that are not children of an
// activity. Both windows have the FLAG_TURNS_SCREEN_ON so both should call wakeup
final WindowToken windowToken = createTestWindowToken(FIRST_SUB_WINDOW, mDisplayContent);
- final WindowState firstWindow = createWindow(null, TYPE_APPLICATION, windowToken,
- "firstWindow");
- final WindowState secondWindow = createWindow(null, TYPE_APPLICATION, windowToken,
- "secondWindow");
+ final WindowState firstWindow = newWindowBuilder("firstWindow",
+ TYPE_APPLICATION).setWindowToken(windowToken).build();
+ final WindowState secondWindow = newWindowBuilder("secondWindow",
+ TYPE_APPLICATION).setWindowToken(windowToken).build();
firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
@@ -459,7 +473,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCanAffectSystemUiFlags() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
app.mActivityRecord.setVisible(true);
assertTrue(app.canAffectSystemUiFlags());
app.mActivityRecord.setVisible(false);
@@ -471,7 +485,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCanAffectSystemUiFlags_starting() {
- final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION_STARTING).build();
app.mActivityRecord.setVisible(true);
app.mStartingData = new SnapshotStartingData(mWm, null, 0);
assertFalse(app.canAffectSystemUiFlags());
@@ -481,7 +495,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCanAffectSystemUiFlags_disallow() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
app.mActivityRecord.setVisible(true);
assertTrue(app.canAffectSystemUiFlags());
app.getTask().setCanAffectSystemUiFlags(false);
@@ -538,9 +552,11 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsSelfOrAncestorWindowAnimating() {
- final WindowState root = createWindow(null, TYPE_APPLICATION, "root");
- final WindowState child1 = createWindow(root, FIRST_SUB_WINDOW, "child1");
- final WindowState child2 = createWindow(child1, FIRST_SUB_WINDOW, "child2");
+ final WindowState root = newWindowBuilder("root", TYPE_APPLICATION).build();
+ final WindowState child1 = newWindowBuilder("child1", FIRST_SUB_WINDOW).setParent(
+ root).build();
+ final WindowState child2 = newWindowBuilder("child2", FIRST_SUB_WINDOW).setParent(
+ child1).build();
assertFalse(child2.isSelfOrAncestorWindowAnimatingExit());
child2.mAnimatingExit = true;
assertTrue(child2.isSelfOrAncestorWindowAnimatingExit());
@@ -551,7 +567,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testDeferredRemovalByAnimating() {
- final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ final WindowState appWindow = newWindowBuilder("appWindow", TYPE_APPLICATION).build();
makeWindowVisible(appWindow);
spyOn(appWindow.mWinAnimator);
doReturn(true).when(appWindow.mWinAnimator).getShown();
@@ -571,8 +587,9 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testOnExitAnimationDone() {
- final WindowState parent = createWindow(null, TYPE_APPLICATION, "parent");
- final WindowState child = createWindow(parent, TYPE_APPLICATION_PANEL, "child");
+ final WindowState parent = newWindowBuilder("parent", TYPE_APPLICATION).build();
+ final WindowState child = newWindowBuilder("child", TYPE_APPLICATION_PANEL).setParent(
+ parent).build();
final SurfaceControl.Transaction t = parent.getPendingTransaction();
child.startAnimation(t, mock(AnimationAdapter.class), false /* hidden */,
SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION);
@@ -609,7 +626,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testLayoutSeqResetOnReparent() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
app.mLayoutSeq = 1;
mDisplayContent.mLayoutSeq = 1;
@@ -622,7 +639,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testDisplayIdUpdatedOnReparent() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
// fake a different display
app.mInputWindowHandle.setDisplayId(mDisplayContent.getDisplayId() + 1);
app.onDisplayChanged(mDisplayContent);
@@ -633,7 +650,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testApplyWithNextDraw() {
- final WindowState win = createWindow(null, TYPE_APPLICATION_OVERLAY, "app");
+ final WindowState win = newWindowBuilder("app", TYPE_APPLICATION_OVERLAY).build();
final SurfaceControl.Transaction[] handledT = { null };
// The normal case that the draw transaction is applied with finishing drawing.
win.applyWithNextDraw(t -> handledT[0] = t);
@@ -657,7 +674,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testSeamlesslyRotateWindow() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
final SurfaceControl.Transaction t = spy(StubTransaction.class);
makeWindowVisible(app);
@@ -707,7 +724,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testVisibilityChangeSwitchUser() {
- final WindowState window = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState window = newWindowBuilder("app", TYPE_APPLICATION).build();
window.mHasSurface = true;
spyOn(window);
doReturn(false).when(window).showForAllUsers();
@@ -729,8 +746,9 @@ public class WindowStateTests extends WindowTestsBase {
final CompatModePackages cmp = mWm.mAtmService.mCompatModePackages;
spyOn(cmp);
doReturn(overrideScale).when(cmp).getCompatScale(anyString(), anyInt());
- final WindowState w = createWindow(null, TYPE_APPLICATION_OVERLAY, "win");
- final WindowState child = createWindow(w, TYPE_APPLICATION_PANEL, "child");
+ final WindowState w = newWindowBuilder("win", TYPE_APPLICATION_OVERLAY).build();
+ final WindowState child = newWindowBuilder("child", TYPE_APPLICATION_PANEL).setParent(
+ w).build();
assertTrue(w.hasCompatScale());
assertTrue(child.hasCompatScale());
@@ -788,7 +806,8 @@ public class WindowStateTests extends WindowTestsBase {
// Child window without scale (e.g. different app) should apply inverse scale of parent.
doReturn(1f).when(cmp).getCompatScale(anyString(), anyInt());
- final WindowState child2 = createWindow(w, TYPE_APPLICATION_SUB_PANEL, "child2");
+ final WindowState child2 = newWindowBuilder("child2", TYPE_APPLICATION_SUB_PANEL).setParent(
+ w).build();
makeWindowVisible(w, child2);
clearInvocations(t);
child2.prepareSurfaces();
@@ -798,10 +817,10 @@ public class WindowStateTests extends WindowTestsBase {
@SetupWindows(addWindows = { W_ABOVE_ACTIVITY, W_NOTIFICATION_SHADE })
@Test
public void testRequestDrawIfNeeded() {
- final WindowState startingApp = createWindow(null /* parent */,
- TYPE_BASE_APPLICATION, "startingApp");
- final WindowState startingWindow = createWindow(null /* parent */,
- TYPE_APPLICATION_STARTING, startingApp.mToken, "starting");
+ final WindowState startingApp = newWindowBuilder("startingApp",
+ TYPE_BASE_APPLICATION).build();
+ final WindowState startingWindow = newWindowBuilder("starting",
+ TYPE_APPLICATION_STARTING).setWindowToken(startingApp.mToken).build();
startingApp.mActivityRecord.mStartingWindow = startingWindow;
final WindowState keyguardHostWindow = mNotificationShadeWindow;
final WindowState allDrawnApp = mAppWindow;
@@ -878,7 +897,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testRequestResizeForBlastSync() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "window");
+ final WindowState win = newWindowBuilder("window", TYPE_APPLICATION).build();
makeWindowVisible(win);
makeLastConfigReportedToClient(win, true /* visible */);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
@@ -926,8 +945,8 @@ public class WindowStateTests extends WindowTestsBase {
final Task task = createTask(mDisplayContent);
final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
- final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
- "App window");
+ final WindowState win = newWindowBuilder("App window", TYPE_APPLICATION).setWindowToken(
+ embeddedActivity).build();
doReturn(true).when(embeddedActivity).isVisible();
embeddedActivity.setVisibleRequested(true);
makeWindowVisible(win);
@@ -949,14 +968,14 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
- final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
+ final WindowState win0 = newWindowBuilder("win0", TYPE_APPLICATION).build();
win0.mActivityRecord.setVisibleRequested(false);
assertFalse(win0.canReceiveTouchInput());
}
@Test
public void testCantReceiveTouchWhenNotFocusable() {
- final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
+ final WindowState win0 = newWindowBuilder("win0", TYPE_APPLICATION).build();
final Task rootTask = win0.mActivityRecord.getRootTask();
spyOn(rootTask);
when(rootTask.shouldIgnoreInput()).thenReturn(true);
@@ -969,7 +988,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testUpdateInputWindowHandle() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build();
win.mAttrs.inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
win.mAttrs.flags = FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH;
final InputWindowHandle handle = new InputWindowHandle(
@@ -982,7 +1001,6 @@ public class WindowStateTests extends WindowTestsBase {
assertTrue(handleWrapper.isChanged());
assertTrue(testFlag(handle.inputConfig, InputConfig.WATCH_OUTSIDE_TOUCH));
- assertFalse(testFlag(handle.inputConfig, InputConfig.PREVENT_SPLITTING));
assertTrue(testFlag(handle.inputConfig, InputConfig.DISABLE_USER_ACTIVITY));
// The window of standard resizable task should not use surface crop as touchable region.
assertFalse(handle.replaceTouchableRegionWithCrop);
@@ -1026,7 +1044,7 @@ public class WindowStateTests extends WindowTestsBase {
@DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
@Test
public void testTouchRegionUsesLetterboxBoundsIfTransformedBoundsAndLetterboxScrolling() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build();
// Transformed bounds used for size of touchable region if letterbox inner bounds are empty.
final Rect transformedBounds = new Rect(0, 0, 300, 500);
@@ -1051,7 +1069,7 @@ public class WindowStateTests extends WindowTestsBase {
@DisableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
@Test
public void testTouchRegionUsesLetterboxBoundsIfNullTransformedBoundsAndLetterboxScrolling() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build();
// Fragment bounds used for size of touchable region if letterbox inner bounds are empty
// and Transform bounds are null.
@@ -1083,7 +1101,7 @@ public class WindowStateTests extends WindowTestsBase {
@EnableFlags(Flags.FLAG_SCROLLING_FROM_LETTERBOX)
@Test
public void testTouchRegionUsesTransformedBoundsIfLetterboxScrolling() {
- final WindowState win = createWindow(null, TYPE_APPLICATION, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).build();
// Transformed bounds used for size of touchable region if letterbox inner bounds are empty.
final Rect transformedBounds = new Rect(0, 0, 300, 500);
@@ -1109,7 +1127,7 @@ public class WindowStateTests extends WindowTestsBase {
public void testHasActiveVisibleWindow() {
final int uid = ActivityBuilder.DEFAULT_FAKE_UID;
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app", uid);
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).setOwnerId(uid).build();
app.mActivityRecord.setVisible(false);
app.mActivityRecord.setVisibility(false);
assertFalse(mAtm.hasActiveVisibleWindow(uid));
@@ -1120,15 +1138,17 @@ public class WindowStateTests extends WindowTestsBase {
// Make the activity invisible and add a visible toast. The uid should have no active
// visible window because toast can be misused by legacy app to bypass background check.
app.mActivityRecord.setVisibility(false);
- final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay", uid);
- final WindowState toast = createWindow(null, TYPE_TOAST, app.mToken, "toast", uid);
+ final WindowState overlay = newWindowBuilder("overlay",
+ TYPE_APPLICATION_OVERLAY).setOwnerId(uid).build();
+ final WindowState toast = newWindowBuilder("toast", TYPE_TOAST).setWindowToken(
+ app.mToken).setOwnerId(uid).build();
toast.onSurfaceShownChanged(true);
assertFalse(mAtm.hasActiveVisibleWindow(uid));
// Though starting window should belong to system. Make sure it is ignored to avoid being
// allow-list unexpectedly, see b/129563343.
- final WindowState starting =
- createWindow(null, TYPE_APPLICATION_STARTING, app.mToken, "starting", uid);
+ final WindowState starting = newWindowBuilder("starting",
+ TYPE_APPLICATION_STARTING).setWindowToken(app.mToken).setOwnerId(uid).build();
starting.onSurfaceShownChanged(true);
assertFalse(mAtm.hasActiveVisibleWindow(uid));
@@ -1145,8 +1165,8 @@ public class WindowStateTests extends WindowTestsBase {
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_notAttached() {
- WindowState sameTokenWindow = createWindow(null, TYPE_BASE_APPLICATION, mAppWindow.mToken,
- "SameTokenWindow");
+ WindowState sameTokenWindow = newWindowBuilder("SameTokenWindow",
+ TYPE_BASE_APPLICATION).setWindowToken(mAppWindow.mToken).build();
mDisplayContent.setImeLayeringTarget(mAppWindow);
makeWindowVisible(mImeWindow);
sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -1158,8 +1178,8 @@ public class WindowStateTests extends WindowTestsBase {
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD })
@Test
public void testNeedsRelativeLayeringToIme_startingWindow() {
- WindowState sameTokenWindow = createWindow(null, TYPE_APPLICATION_STARTING,
- mAppWindow.mToken, "SameTokenWindow");
+ WindowState sameTokenWindow = newWindowBuilder("SameTokenWindow",
+ TYPE_APPLICATION_STARTING).setWindowToken(mAppWindow.mToken).build();
mDisplayContent.setImeLayeringTarget(mAppWindow);
makeWindowVisible(mImeWindow);
sameTokenWindow.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -1169,9 +1189,9 @@ public class WindowStateTests extends WindowTestsBase {
@UseTestDisplay(addWindows = {W_ACTIVITY, W_INPUT_METHOD})
@Test
public void testNeedsRelativeLayeringToIme_systemDialog() {
- WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent,
- "SystemDialog", true);
+ WindowState systemDialogWindow = newWindowBuilder("SystemDialog",
+ TYPE_SECURE_SYSTEM_OVERLAY).setDisplay(
+ mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build();
mDisplayContent.setImeLayeringTarget(mAppWindow);
mAppWindow.getTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
makeWindowVisible(mImeWindow);
@@ -1182,20 +1202,21 @@ public class WindowStateTests extends WindowTestsBase {
@UseTestDisplay(addWindows = {W_INPUT_METHOD})
@Test
public void testNeedsRelativeLayeringToIme_notificationShadeShouldNotHideSystemDialog() {
- WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent,
- "SystemDialog", true);
+ WindowState systemDialogWindow = newWindowBuilder("SystemDialog",
+ TYPE_SECURE_SYSTEM_OVERLAY).setDisplay(
+ mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build();
mDisplayContent.setImeLayeringTarget(systemDialogWindow);
makeWindowVisible(mImeWindow);
- WindowState notificationShade = createWindow(null, TYPE_NOTIFICATION_SHADE,
- mDisplayContent, "NotificationShade", true);
+ WindowState notificationShade = newWindowBuilder("NotificationShade",
+ TYPE_NOTIFICATION_SHADE).setDisplay(
+ mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build();
notificationShade.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
assertFalse(notificationShade.needsRelativeLayeringToIme());
}
@Test
public void testSetFreezeInsetsState() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
spyOn(app);
doReturn(true).when(app).isVisible();
@@ -1216,7 +1237,7 @@ public class WindowStateTests extends WindowTestsBase {
verify(app).notifyInsetsChanged();
// Verify that invisible non-activity window won't dispatch insets changed.
- final WindowState overlay = createWindow(null, TYPE_APPLICATION_OVERLAY, "overlay");
+ final WindowState overlay = newWindowBuilder("overlay", TYPE_APPLICATION_OVERLAY).build();
makeWindowVisible(overlay);
assertTrue(overlay.isReadyToDispatchInsetsState());
overlay.mHasSurface = false;
@@ -1244,9 +1265,9 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testAdjustImeInsetsVisibilityWhenSwitchingApps() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2");
- final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).build();
+ final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_APPLICATION).build();
spyOn(imeWindow);
doReturn(true).when(imeWindow).isVisible();
mDisplayContent.mInputMethodWindow = imeWindow;
@@ -1279,10 +1300,11 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testAdjustImeInsetsVisibilityWhenSwitchingApps_toAppInMultiWindowMode() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
- final WindowState app2 = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
- ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app2");
- final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
+ final WindowState app2 = newWindowBuilder("app2", TYPE_APPLICATION).setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay(
+ mDisplayContent).build();
+ final WindowState imeWindow = newWindowBuilder("imeWindow", TYPE_APPLICATION).build();
spyOn(imeWindow);
doReturn(true).when(imeWindow).isVisible();
mDisplayContent.mInputMethodWindow = imeWindow;
@@ -1321,8 +1343,8 @@ public class WindowStateTests extends WindowTestsBase {
@SetupWindows(addWindows = W_ACTIVITY)
@Test
public void testUpdateImeControlTargetWhenLeavingMultiWindow() {
- WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
- mAppWindow.mToken, "app");
+ WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken(
+ mAppWindow.mToken).build();
mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
spyOn(app);
@@ -1349,8 +1371,8 @@ public class WindowStateTests extends WindowTestsBase {
@SetupWindows(addWindows = { W_ACTIVITY, W_INPUT_METHOD, W_NOTIFICATION_SHADE })
@Test
public void testNotificationShadeHasImeInsetsWhenMultiWindow() {
- WindowState app = createWindow(null, TYPE_BASE_APPLICATION,
- mAppWindow.mToken, "app");
+ WindowState app = newWindowBuilder("app", TYPE_BASE_APPLICATION).setWindowToken(
+ mAppWindow.mToken).build();
// Simulate entering multi-window mode and windowing mode is multi-window.
app.mActivityRecord.getRootTask().setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
@@ -1376,7 +1398,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testRequestedVisibility() {
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
app.mActivityRecord.setVisible(false);
app.mActivityRecord.setVisibility(false);
assertFalse(app.isVisibleRequested());
@@ -1391,7 +1413,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testKeepClearAreas() {
- final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+ final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build();
makeWindowVisible(window);
final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
@@ -1433,7 +1455,7 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testUnrestrictedKeepClearAreas() {
- final WindowState window = createWindow(null, TYPE_APPLICATION, "window");
+ final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).build();
makeWindowVisible(window);
final Rect keepClearArea1 = new Rect(0, 0, 10, 10);
@@ -1481,8 +1503,9 @@ public class WindowStateTests extends WindowTestsBase {
final InputMethodManagerInternal immi = InputMethodManagerInternal.get();
spyOn(immi);
- final WindowState imeTarget = createWindow(null /* parent */, TYPE_BASE_APPLICATION,
- createActivityRecord(mDisplayContent), "imeTarget");
+ final WindowState imeTarget = newWindowBuilder("imeTarget",
+ TYPE_BASE_APPLICATION).setWindowToken(
+ createActivityRecord(mDisplayContent)).build();
imeTarget.mActivityRecord.setVisibleRequested(true);
makeWindowVisible(imeTarget);
@@ -1562,8 +1585,8 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsSecureLocked_flagSecureSet() {
- WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
- 1 /* ownerId */);
+ WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId(
+ 1).build();
window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
assertTrue(window.isSecureLocked());
@@ -1571,8 +1594,8 @@ public class WindowStateTests extends WindowTestsBase {
@Test
public void testIsSecureLocked_flagSecureNotSet() {
- WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
- 1 /* ownerId */);
+ WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId(
+ 1).build();
assertFalse(window.isSecureLocked());
}
@@ -1581,8 +1604,8 @@ public class WindowStateTests extends WindowTestsBase {
public void testIsSecureLocked_disableSecureWindows() {
assumeTrue(Build.IS_DEBUGGABLE);
- WindowState window = createWindow(null /* parent */, TYPE_APPLICATION, "test-window",
- 1 /* ownerId */);
+ WindowState window = newWindowBuilder("test-window", TYPE_APPLICATION).setOwnerId(
+ 1).build();
window.mAttrs.flags |= WindowManager.LayoutParams.FLAG_SECURE;
ContentResolver cr = useFakeSettingsProvider();
@@ -1617,8 +1640,10 @@ public class WindowStateTests extends WindowTestsBase {
String testPackage = "test";
int ownerId1 = 20;
int ownerId2 = 21;
- final WindowState window1 = createWindow(null, TYPE_APPLICATION, "window1", ownerId1);
- final WindowState window2 = createWindow(null, TYPE_APPLICATION, "window2", ownerId2);
+ final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setOwnerId(
+ ownerId1).build();
+ final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setOwnerId(
+ ownerId2).build();
// Setting packagename for targeted feature
window1.mAttrs.packageName = testPackage;
@@ -1638,7 +1663,8 @@ public class WindowStateTests extends WindowTestsBase {
public void testIsSecureLocked_sensitiveContentBlockOrClearScreenCaptureForApp() {
String testPackage = "test";
int ownerId = 20;
- final WindowState window = createWindow(null, TYPE_APPLICATION, "window", ownerId);
+ final WindowState window = newWindowBuilder("window", TYPE_APPLICATION).setOwnerId(
+ ownerId).build();
window.mAttrs.packageName = testPackage;
assertFalse(window.isSecureLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ce0d91264063..37d2a7511d98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -478,7 +478,7 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
private WindowState createCommonWindow(WindowState parent, int type, String name) {
- final WindowState win = createWindow(parent, type, name);
+ final WindowState win = newWindowBuilder(name, type).setParent(parent).build();
// Prevent common windows from been IME targets.
win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
return win;
@@ -502,7 +502,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
- final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+ final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
+ dc).build();
final Binder owner = new Binder();
navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
@@ -513,7 +514,8 @@ public class WindowTestsBase extends SystemServiceTestsBase {
}
WindowState createStatusBarWithProvidedInsets(DisplayContent dc) {
- final WindowState statusBar = createWindow(null, TYPE_STATUS_BAR, dc, "statusBar");
+ final WindowState statusBar = newWindowBuilder("statusBar", TYPE_STATUS_BAR).setDisplay(
+ dc).build();
final Binder owner = new Binder();
statusBar.mAttrs.providedInsets = new InsetsFrameProvider[] {
new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars())
@@ -575,92 +577,13 @@ public class WindowTestsBase extends SystemServiceTestsBase {
WindowState createAppWindow(Task task, int type, String name) {
final ActivityRecord activity = createNonAttachedActivityRecord(task.getDisplayContent());
task.addChild(activity, 0);
- return createWindow(null, type, activity, name);
+ return newWindowBuilder(name, type).setWindowToken(activity).build();
}
- WindowState createDreamWindow(WindowState parent, int type, String name) {
+ WindowState createDreamWindow(String name, int type) {
final WindowToken token = createWindowToken(
mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM, type);
- return createWindow(parent, type, token, name);
- }
-
- // TODO: Move these calls to a builder?
- WindowState createWindow(WindowState parent, int type, String name) {
- return (parent == null)
- ? createWindow(parent, type, mDisplayContent, name)
- : createWindow(parent, type, parent.mToken, name);
- }
-
- WindowState createWindow(WindowState parent, int type, String name, int ownerId) {
- return (parent == null)
- ? createWindow(parent, type, mDisplayContent, name, ownerId)
- : createWindow(parent, type, parent.mToken, name, ownerId);
- }
-
- WindowState createWindow(WindowState parent, int windowingMode, int activityType,
- int type, DisplayContent dc, String name) {
- final WindowToken token = createWindowToken(dc, windowingMode, activityType, type);
- return createWindow(parent, type, token, name);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name) {
- return createWindow(
- parent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type, dc, name);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
- int ownerId) {
- final WindowToken token = createWindowToken(
- dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
- return createWindow(parent, type, token, name, ownerId);
- }
-
- WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
- boolean ownerCanAddInternalSystemWindow) {
- final WindowToken token = createWindowToken(
- dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
- return createWindow(parent, type, token, name, 0 /* ownerId */,
- ownerCanAddInternalSystemWindow);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name) {
- return createWindow(parent, type, token, name, 0 /* ownerId */,
- false /* ownerCanAddInternalSystemWindow */);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId) {
- return createWindow(parent, type, token, name, ownerId,
- false /* ownerCanAddInternalSystemWindow */);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId, boolean ownerCanAddInternalSystemWindow) {
- return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
- mIWindow);
- }
-
- WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
- int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
- return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
- ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
- }
-
- static WindowState createWindow(WindowState parent, int type, WindowToken token,
- String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
- WindowManagerService service, Session session, IWindow iWindow) {
- SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
-
- final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
- attrs.setTitle(name);
- attrs.packageName = "test";
-
- final WindowState w = new WindowState(service, session, iWindow, token, parent,
- OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
- // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
- // adding it to the token...
- token.addWindow(w);
- return w;
+ return newWindowBuilder(name, type).setWindowToken(token).build();
}
static void makeWindowVisible(WindowState... windows) {
@@ -1920,11 +1843,14 @@ public class WindowTestsBase extends SystemServiceTestsBase {
private final WindowManagerService mWMService;
private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
+ private final DisplayContent mDisplayContent;
- TestStartingWindowOrganizer(ActivityTaskManagerService service) {
+ TestStartingWindowOrganizer(ActivityTaskManagerService service,
+ DisplayContent displayContent) {
mAtm = service;
mWMService = mAtm.mWindowManager;
mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
+ mDisplayContent = displayContent;
}
@Override
@@ -1933,10 +1859,11 @@ public class WindowTestsBase extends SystemServiceTestsBase {
final ActivityRecord activity = ActivityRecord.forTokenLocked(info.appToken);
IWindow iWindow = mock(IWindow.class);
doReturn(mock(IBinder.class)).when(iWindow).asBinder();
- final WindowState window = WindowTestsBase.createWindow(null,
- TYPE_APPLICATION_STARTING, activity,
- "Starting window", 0 /* ownerId */, 0 /* userId*/,
- false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
+ // WindowToken is already passed, windowTokenCreator is not needed here.
+ final WindowState window = new WindowTestsBase.WindowStateBuilder("Starting window",
+ TYPE_APPLICATION_STARTING, mWMService, mDisplayContent, iWindow,
+ (unused) -> createTestSession(mAtm),
+ null /* windowTokenCreator */).setWindowToken(activity).build();
activity.mStartingWindow = window;
mAppWindowMap.put(info.appToken, window);
mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
index f226b9d29ca0..a02c3db1e636 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTokenTests.java
@@ -74,11 +74,16 @@ public class WindowTokenTests extends WindowTestsBase {
assertEquals(0, token.getWindowsCount());
- final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
- final WindowState window11 = createWindow(window1, FIRST_SUB_WINDOW, token, "window11");
- final WindowState window12 = createWindow(window1, FIRST_SUB_WINDOW, token, "window12");
- final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
- final WindowState window3 = createWindow(null, TYPE_APPLICATION, token, "window3");
+ final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setWindowToken(
+ token).build();
+ final WindowState window11 = newWindowBuilder("window11", FIRST_SUB_WINDOW).setParent(
+ window1).setWindowToken(token).build();
+ final WindowState window12 = newWindowBuilder("window12", FIRST_SUB_WINDOW).setParent(
+ window1).setWindowToken(token).build();
+ final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setWindowToken(
+ token).build();
+ final WindowState window3 = newWindowBuilder("window3", TYPE_APPLICATION).setWindowToken(
+ token).build();
token.addWindow(window1);
// NOTE: Child windows will not be added to the token as window containers can only
@@ -105,8 +110,10 @@ public class WindowTokenTests extends WindowTestsBase {
public void testAddWindow_assignsLayers() {
final TestWindowToken token1 = createTestWindowToken(0, mDisplayContent);
final TestWindowToken token2 = createTestWindowToken(0, mDisplayContent);
- final WindowState window1 = createWindow(null, TYPE_STATUS_BAR, token1, "window1");
- final WindowState window2 = createWindow(null, TYPE_STATUS_BAR, token2, "window2");
+ final WindowState window1 = newWindowBuilder("window1", TYPE_STATUS_BAR).setWindowToken(
+ token1).build();
+ final WindowState window2 = newWindowBuilder("window2", TYPE_STATUS_BAR).setWindowToken(
+ token2).build();
token1.addWindow(window1);
token2.addWindow(window2);
@@ -122,8 +129,10 @@ public class WindowTokenTests extends WindowTestsBase {
assertEquals(token, dc.getWindowToken(token.token));
- final WindowState window1 = createWindow(null, TYPE_APPLICATION, token, "window1");
- final WindowState window2 = createWindow(null, TYPE_APPLICATION, token, "window2");
+ final WindowState window1 = newWindowBuilder("window1", TYPE_APPLICATION).setWindowToken(
+ token).build();
+ final WindowState window2 = newWindowBuilder("window2", TYPE_APPLICATION).setWindowToken(
+ token).build();
window2.removeImmediately();
// The token should still be mapped in the display content since it still has a child.
@@ -147,8 +156,10 @@ public class WindowTokenTests extends WindowTestsBase {
// Verify that the token is on the display
assertNotNull(mDisplayContent.getWindowToken(token.token));
- final WindowState window1 = createWindow(null, TYPE_TOAST, token, "window1");
- final WindowState window2 = createWindow(null, TYPE_TOAST, token, "window2");
+ final WindowState window1 = newWindowBuilder("window1", TYPE_TOAST).setWindowToken(
+ token).build();
+ final WindowState window2 = newWindowBuilder("window2", TYPE_TOAST).setWindowToken(
+ token).build();
mDisplayContent.removeWindowToken(token.token, true /* animateExit */);
// Verify that the token is no longer mapped on the display
@@ -231,7 +242,8 @@ public class WindowTokenTests extends WindowTestsBase {
assertNull(fromClientToken.mSurfaceControl);
- createWindow(null, TYPE_APPLICATION_OVERLAY, fromClientToken, "window");
+ newWindowBuilder("window", TYPE_APPLICATION_OVERLAY).setWindowToken(
+ fromClientToken).build();
assertNotNull(fromClientToken.mSurfaceControl);
final WindowToken nonClientToken = new WindowToken.Builder(mDisplayContent.mWmService,
@@ -285,7 +297,7 @@ public class WindowTokenTests extends WindowTestsBase {
// Simulate an app window to be the IME layering target, assume the app window has no
// frozen insets state by default.
- final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
+ final WindowState app = newWindowBuilder("app", TYPE_APPLICATION).build();
mDisplayContent.setImeLayeringTarget(app);
assertNull(app.getFrozenInsetsState());
assertTrue(app.isImeLayeringTarget());
@@ -299,7 +311,8 @@ public class WindowTokenTests extends WindowTestsBase {
@Test
public void testRemoveWindowToken_noAnimateExitWhenSet() {
final TestWindowToken token = createTestWindowToken(0, mDisplayContent);
- final WindowState win = createWindow(null, TYPE_APPLICATION, token, "win");
+ final WindowState win = newWindowBuilder("win", TYPE_APPLICATION).setWindowToken(
+ token).build();
makeWindowVisible(win);
assertTrue(win.isOnScreen());
spyOn(win);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 4f60106db93d..84e21181a7b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -221,7 +221,7 @@ public class ZOrderingTests extends WindowTestsBase {
}
WindowState createWindow(String name) {
- return createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, name);
+ return newWindowBuilder(name, TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build();
}
@Test
@@ -263,12 +263,12 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testAssignWindowLayers_ForImeWithAppTargetWithChildWindows() {
final WindowState imeAppTarget = createWindow("imeAppTarget");
- final WindowState imeAppTargetChildAboveWindow = createWindow(imeAppTarget,
- TYPE_APPLICATION_ATTACHED_DIALOG, imeAppTarget.mToken,
- "imeAppTargetChildAboveWindow");
- final WindowState imeAppTargetChildBelowWindow = createWindow(imeAppTarget,
- TYPE_APPLICATION_MEDIA_OVERLAY, imeAppTarget.mToken,
- "imeAppTargetChildBelowWindow");
+ final WindowState imeAppTargetChildAboveWindow = newWindowBuilder(
+ "imeAppTargetChildAboveWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent(
+ imeAppTarget).setWindowToken(imeAppTarget.mToken).build();
+ final WindowState imeAppTargetChildBelowWindow = newWindowBuilder(
+ "imeAppTargetChildBelowWindow", TYPE_APPLICATION_MEDIA_OVERLAY).setParent(
+ imeAppTarget).setWindowToken(imeAppTarget.mToken).build();
mDisplayContent.setImeLayeringTarget(imeAppTarget);
makeWindowVisible(mImeWindow);
@@ -313,9 +313,9 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testAssignWindowLayers_ForImeNonAppImeTarget() {
- final WindowState imeSystemOverlayTarget = createWindow(null, TYPE_SYSTEM_OVERLAY,
- mDisplayContent, "imeSystemOverlayTarget",
- true /* ownerCanAddInternalSystemWindow */);
+ final WindowState imeSystemOverlayTarget = newWindowBuilder("imeSystemOverlayTarget",
+ TYPE_SYSTEM_OVERLAY).setDisplay(mDisplayContent).setOwnerCanAddInternalSystemWindow(
+ true).build();
mDisplayContent.setImeLayeringTarget(imeSystemOverlayTarget);
mDisplayContent.assignChildLayers(mTransaction);
@@ -354,18 +354,19 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testStackLayers() {
final WindowState anyWindow1 = createWindow("anyWindow");
- final WindowState pinnedStackWindow = createWindow(null, WINDOWING_MODE_PINNED,
- ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION, mDisplayContent,
- "pinnedStackWindow");
- final WindowState dockedStackWindow = createWindow(null,
- WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
- mDisplayContent, "dockedStackWindow");
- final WindowState assistantStackWindow = createWindow(null,
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_ASSISTANT, TYPE_BASE_APPLICATION,
- mDisplayContent, "assistantStackWindow");
- final WindowState homeActivityWindow = createWindow(null, WINDOWING_MODE_FULLSCREEN,
- ACTIVITY_TYPE_HOME, TYPE_BASE_APPLICATION,
- mDisplayContent, "homeActivityWindow");
+ final WindowState pinnedStackWindow = newWindowBuilder("pinnedStackWindow",
+ TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_PINNED).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(mDisplayContent).build();
+ final WindowState dockedStackWindow = newWindowBuilder("dockedStackWindow",
+ TYPE_BASE_APPLICATION).setWindowingMode(
+ WINDOWING_MODE_MULTI_WINDOW).setActivityType(ACTIVITY_TYPE_STANDARD).setDisplay(
+ mDisplayContent).build();
+ final WindowState assistantStackWindow = newWindowBuilder("assistantStackWindow",
+ TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_FULLSCREEN).setActivityType(
+ ACTIVITY_TYPE_ASSISTANT).setDisplay(mDisplayContent).build();
+ final WindowState homeActivityWindow = newWindowBuilder("homeActivityWindow",
+ TYPE_BASE_APPLICATION).setWindowingMode(WINDOWING_MODE_FULLSCREEN).setActivityType(
+ ACTIVITY_TYPE_HOME).setDisplay(mDisplayContent).build();
final WindowState anyWindow2 = createWindow("anyWindow2");
mDisplayContent.assignChildLayers(mTransaction);
@@ -383,13 +384,12 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testAssignWindowLayers_ForSysUiPanels() {
- final WindowState navBarPanel =
- createWindow(null, TYPE_NAVIGATION_BAR_PANEL, mDisplayContent, "NavBarPanel");
- final WindowState statusBarPanel =
- createWindow(null, TYPE_STATUS_BAR_ADDITIONAL, mDisplayContent,
- "StatusBarAdditional");
- final WindowState statusBarSubPanel =
- createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, mDisplayContent, "StatusBarSubPanel");
+ final WindowState navBarPanel = newWindowBuilder("NavBarPanel",
+ TYPE_NAVIGATION_BAR_PANEL).setDisplay(mDisplayContent).build();
+ final WindowState statusBarPanel = newWindowBuilder("StatusBarAdditional",
+ TYPE_STATUS_BAR_ADDITIONAL).setDisplay(mDisplayContent).build();
+ final WindowState statusBarSubPanel = newWindowBuilder("StatusBarSubPanel",
+ TYPE_STATUS_BAR_SUB_PANEL).setDisplay(mDisplayContent).build();
mDisplayContent.assignChildLayers(mTransaction);
// Ime should be above all app windows and below system windows if it is targeting an app
@@ -401,15 +401,16 @@ public class ZOrderingTests extends WindowTestsBase {
@Test
public void testAssignWindowLayers_ForImeOnPopupImeLayeringTarget() {
- final WindowState imeAppTarget = createWindow(null, TYPE_APPLICATION,
- mAppWindow.mActivityRecord, "imeAppTarget");
+ final WindowState imeAppTarget = newWindowBuilder("imeAppTarget",
+ TYPE_APPLICATION).setWindowToken(mAppWindow.mActivityRecord).build();
mDisplayContent.setImeInputTarget(imeAppTarget);
mDisplayContent.setImeLayeringTarget(imeAppTarget);
mDisplayContent.setImeControlTarget(imeAppTarget);
// Set a popup IME layering target and keeps the original IME control target behinds it.
- final WindowState popupImeTargetWin = createWindow(imeAppTarget,
- TYPE_APPLICATION_SUB_PANEL, mAppWindow.mActivityRecord, "popupImeTargetWin");
+ final WindowState popupImeTargetWin = newWindowBuilder("popupImeTargetWin",
+ TYPE_APPLICATION_SUB_PANEL).setParent(imeAppTarget).setWindowToken(
+ mAppWindow.mActivityRecord).build();
mDisplayContent.setImeLayeringTarget(popupImeTargetWin);
mDisplayContent.updateImeParent();
@@ -424,11 +425,11 @@ public class ZOrderingTests extends WindowTestsBase {
// then we can drop all negative layering on the windowing side.
final WindowState anyWindow = createWindow("anyWindow");
- final WindowState child = createWindow(anyWindow, TYPE_APPLICATION_MEDIA, mDisplayContent,
- "TypeApplicationMediaChild");
- final WindowState mediaOverlayChild = createWindow(anyWindow,
- TYPE_APPLICATION_MEDIA_OVERLAY,
- mDisplayContent, "TypeApplicationMediaOverlayChild");
+ final WindowState child = newWindowBuilder("TypeApplicationMediaChild",
+ TYPE_APPLICATION_MEDIA).setParent(anyWindow).setDisplay(mDisplayContent).build();
+ final WindowState mediaOverlayChild = newWindowBuilder("TypeApplicationMediaOverlayChild",
+ TYPE_APPLICATION_MEDIA_OVERLAY).setParent(anyWindow).setDisplay(
+ mDisplayContent).build();
mDisplayContent.assignChildLayers(mTransaction);
@@ -440,14 +441,17 @@ public class ZOrderingTests extends WindowTestsBase {
public void testAssignWindowLayers_ForPostivelyZOrderedSubtype() {
final WindowState anyWindow = createWindow("anyWindow");
final ArrayList<WindowState> childList = new ArrayList<>();
- childList.add(createWindow(anyWindow, TYPE_APPLICATION_PANEL, mDisplayContent,
- "TypeApplicationPanelChild"));
- childList.add(createWindow(anyWindow, TYPE_APPLICATION_SUB_PANEL, mDisplayContent,
- "TypeApplicationSubPanelChild"));
- childList.add(createWindow(anyWindow, TYPE_APPLICATION_ATTACHED_DIALOG, mDisplayContent,
- "TypeApplicationAttachedDialogChild"));
- childList.add(createWindow(anyWindow, TYPE_APPLICATION_ABOVE_SUB_PANEL, mDisplayContent,
- "TypeApplicationAboveSubPanelPanelChild"));
+ childList.add(newWindowBuilder("TypeApplicationPanelChild",
+ TYPE_APPLICATION_PANEL).setParent(anyWindow).setDisplay(mDisplayContent).build());
+ childList.add(newWindowBuilder("TypeApplicationSubPanelChild",
+ TYPE_APPLICATION_SUB_PANEL).setParent(anyWindow).setDisplay(
+ mDisplayContent).build());
+ childList.add(newWindowBuilder("TypeApplicationAttachedDialogChild",
+ TYPE_APPLICATION_ATTACHED_DIALOG).setParent(anyWindow).setDisplay(
+ mDisplayContent).build());
+ childList.add(newWindowBuilder("TypeApplicationAboveSubPanelPanelChild",
+ TYPE_APPLICATION_ABOVE_SUB_PANEL).setParent(anyWindow).setDisplay(
+ mDisplayContent).build());
final LayerRecordingTransaction t = mTransaction;
mDisplayContent.assignChildLayers(t);
@@ -469,8 +473,8 @@ public class ZOrderingTests extends WindowTestsBase {
// Create a popupWindow
assertWindowHigher(mImeWindow, mAppWindow);
- final WindowState popupWindow = createWindow(mAppWindow, TYPE_APPLICATION_PANEL,
- mDisplayContent, "PopupWindow");
+ final WindowState popupWindow = newWindowBuilder("PopupWindow",
+ TYPE_APPLICATION_PANEL).setParent(mAppWindow).setDisplay(mDisplayContent).build();
spyOn(popupWindow);
mDisplayContent.assignChildLayers(mTransaction);
@@ -492,8 +496,9 @@ public class ZOrderingTests extends WindowTestsBase {
makeWindowVisible(mImeWindow);
// Create a popupWindow
- final WindowState systemDialogWindow = createWindow(null, TYPE_SECURE_SYSTEM_OVERLAY,
- mDisplayContent, "SystemDialog", true);
+ final WindowState systemDialogWindow = newWindowBuilder("SystemDialog",
+ TYPE_SECURE_SYSTEM_OVERLAY).setDisplay(
+ mDisplayContent).setOwnerCanAddInternalSystemWindow(true).build();
systemDialogWindow.mAttrs.flags |= FLAG_ALT_FOCUSABLE_IM;
spyOn(systemDialogWindow);
diff --git a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
index d49214ab718b..a9ae5f7dfc3f 100644
--- a/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
+++ b/services/texttospeech/java/com/android/server/texttospeech/TextToSpeechManagerPerUserService.java
@@ -16,6 +16,10 @@
package com.android.server.texttospeech;
+import static android.content.Context.BIND_AUTO_CREATE;
+import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
import static com.android.internal.infra.AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
import android.annotation.NonNull;
@@ -95,7 +99,7 @@ final class TextToSpeechManagerPerUserService extends
ITextToSpeechSessionCallback callback) {
super(context,
new Intent(TextToSpeech.Engine.INTENT_ACTION_TTS_SERVICE).setPackage(engine),
- Context.BIND_AUTO_CREATE | Context.BIND_SCHEDULE_LIKE_TOP_APP,
+ BIND_AUTO_CREATE | BIND_SCHEDULE_LIKE_TOP_APP | BIND_FOREGROUND_SERVICE,
userId,
ITextToSpeechService.Stub::asInterface);
mEngine = engine;
diff --git a/telephony/java/android/telephony/CellularIdentifierDisclosure.java b/telephony/java/android/telephony/CellularIdentifierDisclosure.java
index 0b6a70feac9d..92c51ec9e84f 100644
--- a/telephony/java/android/telephony/CellularIdentifierDisclosure.java
+++ b/telephony/java/android/telephony/CellularIdentifierDisclosure.java
@@ -74,6 +74,14 @@ public final class CellularIdentifierDisclosure implements Parcelable {
/** IMEI DETATCH INDICATION. Reference: 3GPP TS 24.008 9.2.14.
* Applies to 2g and 3g networks. Used for circuit-switched detach. */
public static final int NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION = 11;
+ /** Vendor-specific enumeration to identify a disclosure as potentially benign.
+ * Enables vendors to semantically classify disclosures based on their own logic. */
+ @FlaggedApi(Flags.FLAG_VENDOR_SPECIFIC_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS)
+ public static final int NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_FALSE = 12;
+ /** Vendor-specific enumeration to identify a disclosure as potentially harmful.
+ * Enables vendors to semantically classify disclosures based on their own logic. */
+ @FlaggedApi(Flags.FLAG_VENDOR_SPECIFIC_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS)
+ public static final int NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_TRUE = 13;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -84,7 +92,9 @@ public final class CellularIdentifierDisclosure implements Parcelable {
NAS_PROTOCOL_MESSAGE_AUTHENTICATION_AND_CIPHERING_RESPONSE,
NAS_PROTOCOL_MESSAGE_REGISTRATION_REQUEST, NAS_PROTOCOL_MESSAGE_DEREGISTRATION_REQUEST,
NAS_PROTOCOL_MESSAGE_CM_REESTABLISHMENT_REQUEST,
- NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST, NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION})
+ NAS_PROTOCOL_MESSAGE_CM_SERVICE_REQUEST, NAS_PROTOCOL_MESSAGE_IMSI_DETACH_INDICATION,
+ NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_FALSE,
+ NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_TRUE})
public @interface NasProtocolMessage {
}
@@ -156,6 +166,14 @@ public final class CellularIdentifierDisclosure implements Parcelable {
return mIsEmergency;
}
+ /**
+ * @return if the modem vendor classifies the disclosure as benign.
+ */
+ @FlaggedApi(Flags.FLAG_VENDOR_SPECIFIC_CELLULAR_IDENTIFIER_DISCLOSURE_INDICATIONS)
+ public boolean isBenign() {
+ return mNasProtocolMessage == NAS_PROTOCOL_MESSAGE_THREAT_IDENTIFIER_FALSE;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
index b5dfb631609c..e18fad3eda79 100644
--- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java
@@ -78,6 +78,9 @@ public interface SatelliteTransmissionUpdateCallback {
/**
* Called when framework receives a request to send a datagram.
*
+ * Informs external apps that device is working on sending a datagram out and is in the process
+ * of checking if all the conditions required to send datagrams are met.
+ *
* @param datagramType The type of the requested datagram.
*/
@FlaggedApi(Flags.FLAG_SATELLITE_SYSTEM_APIS)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 4d7085feb98f..d35c9008e8cb 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -830,6 +830,18 @@ class KeyGestureControllerTests {
KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
+ TestData(
+ "META + ALT + 'V' -> Toggle Voice Access",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_V
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_VOICE_ACCESS,
+ intArrayOf(KeyEvent.KEYCODE_V),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@@ -843,6 +855,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -861,6 +874,7 @@ class KeyGestureControllerTests {
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
+ com.android.hardware.input.Flags.FLAG_ENABLE_VOICE_ACCESS_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
diff --git a/tools/aapt2/cmd/Command.cpp b/tools/aapt2/cmd/Command.cpp
index 449d93dd8c0b..20315561cceb 100644
--- a/tools/aapt2/cmd/Command.cpp
+++ b/tools/aapt2/cmd/Command.cpp
@@ -53,61 +53,67 @@ std::string GetSafePath(StringPiece arg) {
void Command::AddRequiredFlag(StringPiece name, StringPiece description, std::string* value,
uint32_t flags) {
- auto func = [value, flags](StringPiece arg) -> bool {
+ auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
*value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func)));
}
void Command::AddRequiredFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
- auto func = [value, flags](StringPiece arg) -> bool {
+ auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ true, /* num_args */ 1, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ true, /* num_args */ 1, std::move(func)));
}
void Command::AddOptionalFlag(StringPiece name, StringPiece description,
std::optional<std::string>* value, uint32_t flags) {
- auto func = [value, flags](StringPiece arg) -> bool {
+ auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
*value = (flags & Command::kPath) ? GetSafePath(arg) : std::string(arg);
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
}
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::vector<std::string>* value, uint32_t flags) {
- auto func = [value, flags](StringPiece arg) -> bool {
+ auto func = [value, flags](StringPiece arg, std::ostream*) -> bool {
value->push_back((flags & Command::kPath) ? GetSafePath(arg) : std::string(arg));
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
}
void Command::AddOptionalFlagList(StringPiece name, StringPiece description,
std::unordered_set<std::string>* value) {
- auto func = [value](StringPiece arg) -> bool {
+ auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
value->emplace(arg);
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 1, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ false, /* num_args */ 1, std::move(func)));
}
void Command::AddOptionalSwitch(StringPiece name, StringPiece description, bool* value) {
- auto func = [value](StringPiece arg) -> bool {
+ auto func = [value](StringPiece arg, std::ostream* out_error) -> bool {
*value = true;
return true;
};
- flags_.emplace_back(Flag(name, description, /* required */ false, /* num_args */ 0, func));
+ flags_.emplace_back(
+ Flag(name, description, /* required */ false, /* num_args */ 0, std::move(func)));
}
void Command::AddOptionalSubcommand(std::unique_ptr<Command>&& subcommand, bool experimental) {
@@ -172,19 +178,74 @@ void Command::Usage(std::ostream* out) {
argline = " ";
}
}
- *out << " " << std::setw(kWidth) << std::left << "-h"
- << "Displays this help menu\n";
out->flush();
}
-int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_error) {
+const std::string& Command::addEnvironmentArg(const Flag& flag, const char* env) {
+ if (*env && flag.num_args > 0) {
+ return environment_args_.emplace_back(flag.name + '=' + env);
+ }
+ return flag.name;
+}
+
+//
+// Looks for the flags specified in the environment and adds them to |args|.
+// Expected format:
+// - _AAPT2_UPPERCASE_NAME are added before all of the command line flags, so it's
+// a default for the flag that may get overridden by the command line.
+// - AAPT2_UPPERCASE_NAME_ are added after them, making this to be the final value
+// even if there was something on the command line.
+// - All dashes in the flag name get replaced with underscores, the rest of it is
+// intact.
+//
+// E.g.
+// --set-some-flag becomes either _AAPT2_SET_SOME_FLAG or AAPT2_SET_SOME_FLAG_
+// --set-param=2 is _AAPT2_SET_SOME_FLAG=2
+//
+// Values get passed as it, with no processing or quoting.
+//
+// This way one can make sure aapt2 has the flags they need even when it is
+// launched in a way they can't control, e.g. deep inside a build.
+//
+void Command::parseFlagsFromEnvironment(std::vector<StringPiece>& args) {
+ // If the first argument is a subcommand then skip it and prepend the flags past that (the root
+ // command should only have a single '-h' flag anyway).
+ const int insert_pos = args.empty() ? 0 : args.front().starts_with('-') ? 0 : 1;
+
+ std::string env_name;
+ for (const Flag& flag : flags_) {
+ // First, the prefix version.
+ env_name.assign("_AAPT2_");
+ // Append the uppercased flag name, skipping all dashes in front and replacing them with
+ // underscores later.
+ auto name_start = flag.name.begin();
+ while (name_start != flag.name.end() && *name_start == '-') {
+ ++name_start;
+ }
+ std::transform(name_start, flag.name.end(), std::back_inserter(env_name),
+ [](char c) { return c == '-' ? '_' : toupper(c); });
+ if (auto prefix_env = getenv(env_name.c_str())) {
+ args.insert(args.begin() + insert_pos, addEnvironmentArg(flag, prefix_env));
+ }
+ // Now reuse the same name variable to construct a suffix version: append the
+ // underscore and just skip the one in front.
+ env_name += '_';
+ if (auto suffix_env = getenv(env_name.c_str() + 1)) {
+ args.push_back(addEnvironmentArg(flag, suffix_env));
+ }
+ }
+}
+
+int Command::Execute(std::vector<StringPiece>& args, std::ostream* out_error) {
TRACE_NAME_ARGS("Command::Execute", args);
std::vector<std::string> file_args;
+ parseFlagsFromEnvironment(args);
+
for (size_t i = 0; i < args.size(); i++) {
StringPiece arg = args[i];
if (*(arg.data()) != '-') {
- // Continue parsing as the subcommand if the first argument matches one of the subcommands
+ // Continue parsing as a subcommand if the first argument matches one of the subcommands
if (i == 0) {
for (auto& subcommand : subcommands_) {
if (arg == subcommand->name_ || (!subcommand->short_name_.empty()
@@ -211,37 +272,67 @@ int Command::Execute(const std::vector<StringPiece>& args, std::ostream* out_err
return 1;
}
+ static constexpr auto matchShortArg = [](std::string_view arg, const Flag& flag) static {
+ return flag.name.starts_with("--") &&
+ arg.compare(0, 2, std::string_view(flag.name.c_str() + 1, 2)) == 0;
+ };
+
bool match = false;
for (Flag& flag : flags_) {
- // Allow both "--arg value" and "--arg=value" syntax.
+ // Allow both "--arg value" and "--arg=value" syntax, and look for the cases where we can
+ // safely deduce the "--arg" flag from the short "-a" version when there's no value expected
+ bool matched_current = false;
if (arg.starts_with(flag.name) &&
(arg.size() == flag.name.size() || (flag.num_args > 0 && arg[flag.name.size()] == '='))) {
- if (flag.num_args > 0) {
- if (arg.size() == flag.name.size()) {
- i++;
- if (i >= args.size()) {
- *out_error << flag.name << " missing argument.\n\n";
- Usage(out_error);
- return 1;
- }
- arg = args[i];
- } else {
- arg.remove_prefix(flag.name.size() + 1);
- // Disallow empty arguments after '='.
- if (arg.empty()) {
- *out_error << flag.name << " has empty argument.\n\n";
- Usage(out_error);
- return 1;
- }
+ matched_current = true;
+ } else if (flag.num_args == 0 && matchShortArg(arg, flag)) {
+ matched_current = true;
+ // It matches, now need to make sure no other flag would match as well.
+ // This is really inefficient, but we don't expect to have enough flags for it to matter
+ // (famous last words).
+ for (const Flag& other_flag : flags_) {
+ if (&other_flag == &flag) {
+ continue;
+ }
+ if (matchShortArg(arg, other_flag)) {
+ matched_current = false; // ambiguous, skip this match
+ break;
+ }
+ }
+ }
+ if (!matched_current) {
+ continue;
+ }
+
+ if (flag.num_args > 0) {
+ if (arg.size() == flag.name.size()) {
+ i++;
+ if (i >= args.size()) {
+ *out_error << flag.name << " missing argument.\n\n";
+ Usage(out_error);
+ return 1;
}
- flag.action(arg);
+ arg = args[i];
} else {
- flag.action({});
+ arg.remove_prefix(flag.name.size() + 1);
+ // Disallow empty arguments after '='.
+ if (arg.empty()) {
+ *out_error << flag.name << " has empty argument.\n\n";
+ Usage(out_error);
+ return 1;
+ }
+ }
+ if (!flag.action(arg, out_error)) {
+ return 1;
+ }
+ } else {
+ if (!flag.action({}, out_error)) {
+ return 1;
}
- flag.found = true;
- match = true;
- break;
}
+ flag.found = true;
+ match = true;
+ break;
}
if (!match) {
diff --git a/tools/aapt2/cmd/Command.h b/tools/aapt2/cmd/Command.h
index 1416e980ed19..767ca9b0de9f 100644
--- a/tools/aapt2/cmd/Command.h
+++ b/tools/aapt2/cmd/Command.h
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-#ifndef AAPT_COMMAND_H
-#define AAPT_COMMAND_H
+#pragma once
+#include <deque>
#include <functional>
+#include <memory>
#include <optional>
#include <ostream>
#include <string>
@@ -30,10 +31,17 @@ namespace aapt {
class Command {
public:
- explicit Command(android::StringPiece name) : name_(name), full_subcommand_name_(name){};
+ explicit Command(android::StringPiece name) : Command(name, {}) {
+ }
explicit Command(android::StringPiece name, android::StringPiece short_name)
- : name_(name), short_name_(short_name), full_subcommand_name_(name){};
+ : name_(name), short_name_(short_name), full_subcommand_name_(name) {
+ flags_.emplace_back("--help", "Displays this help menu", false, 0,
+ [this](android::StringPiece arg, std::ostream* out) {
+ Usage(out);
+ return false;
+ });
+ }
Command(Command&&) = default;
Command& operator=(Command&&) = default;
@@ -76,41 +84,51 @@ class Command {
// Parses the command line arguments, sets the flag variable values, and runs the action of
// the command. If the arguments fail to parse to the command and its subcommands, then the action
// will not be run and the usage will be printed instead.
- int Execute(const std::vector<android::StringPiece>& args, std::ostream* outError);
+ int Execute(std::vector<android::StringPiece>& args, std::ostream* out_error);
+
+ // Same, but for a temporary vector of args.
+ int Execute(std::vector<android::StringPiece>&& args, std::ostream* out_error) {
+ return Execute(args, out_error);
+ }
// The action to preform when the command is executed.
virtual int Action(const std::vector<std::string>& args) = 0;
private:
struct Flag {
- explicit Flag(android::StringPiece name, android::StringPiece description,
- const bool is_required, const size_t num_args,
- std::function<bool(android::StringPiece value)>&& action)
+ explicit Flag(android::StringPiece name, android::StringPiece description, bool is_required,
+ const size_t num_args,
+ std::function<bool(android::StringPiece value, std::ostream* out_err)>&& action)
: name(name),
description(description),
- is_required(is_required),
+ action(std::move(action)),
num_args(num_args),
- action(std::move(action)) {
+ is_required(is_required) {
}
- const std::string name;
- const std::string description;
- const bool is_required;
- const size_t num_args;
- const std::function<bool(android::StringPiece value)> action;
+ std::string name;
+ std::string description;
+ std::function<bool(android::StringPiece value, std::ostream* out_error)> action;
+ size_t num_args;
+ bool is_required;
bool found = false;
};
+ const std::string& addEnvironmentArg(const Flag& flag, const char* env);
+ void parseFlagsFromEnvironment(std::vector<android::StringPiece>& args);
+
std::string name_;
std::string short_name_;
- std::string description_ = "";
+ std::string description_;
std::string full_subcommand_name_;
std::vector<Flag> flags_;
std::vector<std::unique_ptr<Command>> subcommands_;
std::vector<std::unique_ptr<Command>> experimental_subcommands_;
+ // A collection of arguments loaded from environment variables, with stable positions
+ // in memory - we add them to the vector of string views so the pointers may not change,
+ // with or without short string buffer utilization in std::string.
+ std::deque<std::string> environment_args_;
};
} // namespace aapt
-
-#endif // AAPT_COMMAND_H
diff --git a/tools/aapt2/cmd/Command_test.cpp b/tools/aapt2/cmd/Command_test.cpp
index 20d87e0025c3..2a3cb2a0c65d 100644
--- a/tools/aapt2/cmd/Command_test.cpp
+++ b/tools/aapt2/cmd/Command_test.cpp
@@ -118,4 +118,45 @@ TEST(CommandTest, OptionsWithValues) {
EXPECT_NE(0, command.Execute({"--flag1"s, "2"s}, &std::cerr));
}
+TEST(CommandTest, ShortOptions) {
+ TestCommand command;
+ bool flag = false;
+ command.AddOptionalSwitch("--flag", "", &flag);
+
+ ASSERT_EQ(0, command.Execute({"--flag"s}, &std::cerr));
+ EXPECT_TRUE(flag);
+
+ // Short version of a switch should work.
+ flag = false;
+ ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr));
+ EXPECT_TRUE(flag);
+
+ // Ambiguous names shouldn't parse via short options.
+ command.AddOptionalSwitch("--flag-2", "", &flag);
+ ASSERT_NE(0, command.Execute({"-f"s}, &std::cerr));
+
+ // But when we have a proper flag like that it should still work.
+ flag = false;
+ command.AddOptionalSwitch("-f", "", &flag);
+ ASSERT_EQ(0, command.Execute({"-f"s}, &std::cerr));
+ EXPECT_TRUE(flag);
+
+ // A regular short flag works fine as well.
+ flag = false;
+ command.AddOptionalSwitch("-d", "", &flag);
+ ASSERT_EQ(0, command.Execute({"-d"s}, &std::cerr));
+ EXPECT_TRUE(flag);
+
+ // A flag with a value only works via its long name syntax.
+ std::optional<std::string> val;
+ command.AddOptionalFlag("--with-val", "", &val);
+ ASSERT_EQ(0, command.Execute({"--with-val"s, "1"s}, &std::cerr));
+ EXPECT_TRUE(val);
+ EXPECT_STREQ("1", val->c_str());
+
+ // Make sure the flags that require a value can't be parsed via short syntax, -w=blah
+ // looks weird.
+ ASSERT_NE(0, command.Execute({"-w"s, "2"s}, &std::cerr));
+}
+
} // namespace aapt \ No newline at end of file