summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android-sdk-flags/OWNERS1
-rw-r--r--android-sdk-flags/flags.aconfig1
-rw-r--r--apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java4
-rw-r--r--apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java10
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl1
-rw-r--r--apex/jobscheduler/framework/java/android/app/job/JobScheduler.java28
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java204
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java3
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java110
-rw-r--r--config/preloaded-classes-denylist11
-rw-r--r--core/api/current.txt16
-rw-r--r--core/api/system-current.txt13
-rw-r--r--core/java/android/app/ActivityManager.java13
-rw-r--r--core/java/android/app/ActivityTaskManager.java5
-rw-r--r--core/java/android/app/ActivityThread.java57
-rw-r--r--core/java/android/app/AppCompatTaskInfo.java7
-rw-r--r--core/java/android/app/AppOpsManager.java470
-rw-r--r--core/java/android/app/StatusBarManager.java24
-rw-r--r--core/java/android/app/TaskInfo.java30
-rw-r--r--core/java/android/app/admin/DevicePolicyIdentifiers.java6
-rw-r--r--core/java/android/app/jank/JankTracker.java219
-rw-r--r--core/java/android/appwidget/flags.aconfig2
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceParams.java132
-rw-r--r--core/java/android/companion/virtual/flags.aconfig7
-rw-r--r--core/java/android/content/res/TEST_MAPPING3
-rw-r--r--core/java/android/content/res/flags.aconfig12
-rw-r--r--core/java/android/database/BulkCursorNative.java2
-rw-r--r--core/java/android/database/sqlite/flags.aconfig3
-rw-r--r--core/java/android/hardware/DataSpace.java35
-rw-r--r--core/java/android/hardware/camera2/CameraCharacteristics.java116
-rw-r--r--core/java/android/hardware/camera2/impl/CameraMetadataNative.java40
-rw-r--r--core/java/android/hardware/camera2/params/StreamConfigurationMap.java143
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java7
-rw-r--r--core/java/android/hardware/display/IVirtualDisplayCallback.aidl5
-rw-r--r--core/java/android/hardware/display/VirtualDisplay.java22
-rw-r--r--core/java/android/hardware/display/VirtualDisplayConfig.java63
-rw-r--r--core/java/android/hardware/input/IInputManager.aidl2
-rw-r--r--core/java/android/hardware/input/InputManager.java30
-rw-r--r--core/java/android/hardware/input/InputSettings.java17
-rw-r--r--core/java/android/hardware/soundtrigger/SoundTrigger.java4
-rw-r--r--core/java/android/hardware/usb/OWNERS6
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java34
-rw-r--r--core/java/android/inputmethodservice/NavigationBarController.java28
-rw-r--r--core/java/android/os/BatteryUsageStats.java2
-rw-r--r--core/java/android/os/Build.java6
-rw-r--r--core/java/android/os/ParcelFileDescriptor.java8
-rw-r--r--core/java/android/os/Process.java124
-rw-r--r--core/java/android/os/StrictMode.java136
-rw-r--r--core/java/android/permission/flags.aconfig35
-rw-r--r--core/java/android/print/OWNERS1
-rw-r--r--core/java/android/printservice/OWNERS1
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java6
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java4
-rw-r--r--core/java/android/view/InsetsSourceControl.java3
-rw-r--r--core/java/android/view/SurfaceControl.java3
-rw-r--r--core/java/android/view/View.java101
-rw-r--r--core/java/android/view/WindowManager.java25
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java9
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig7
-rw-r--r--core/java/android/view/inputmethod/ImeTracker.java6
-rw-r--r--core/java/android/view/inputmethod/InputMethodSubtype.java125
-rw-r--r--core/java/android/window/TransitionInfo.java9
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig15
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/java/com/android/internal/os/KernelSingleUidTimeReader.java21
-rw-r--r--core/java/com/android/internal/os/LongArrayMultiStateCounter.java233
-rw-r--r--core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java51
-rw-r--r--core/jni/android_view_SurfaceControl.cpp6
-rw-r--r--core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp56
-rw-r--r--core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp261
-rw-r--r--core/jni/com_android_internal_os_LongMultiStateCounter.cpp8
-rw-r--r--core/proto/android/server/windowmanagerservice.proto1
-rw-r--r--core/proto/android/service/appwidget.proto13
-rw-r--r--core/res/res/drawable-watch/ic_lock_bugreport.xml17
-rw-r--r--core/res/res/drawable-watch/ic_lock_power_off.xml13
-rw-r--r--core/res/res/drawable-watch/ic_restart.xml13
-rw-r--r--core/res/res/drawable-watch/ic_settings.xml19
-rw-r--r--core/res/res/layout/input_method_switch_item_new.xml23
-rw-r--r--core/res/res/values/attrs.xml4
-rw-r--r--core/res/res/values/config_telephony.xml5
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/res/res/values/strings.xml48
-rw-r--r--core/res/res/values/symbols.xml24
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java35
-rw-r--r--core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java22
-rw-r--r--core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java93
-rw-r--r--graphics/java/android/graphics/ImageFormat.java18
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java4
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt96
-rw-r--r--libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt27
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl)2
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java (renamed from libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java)168
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java8
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt20
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt5
-rw-r--r--libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java1
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java56
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java34
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt95
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt25
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java124
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java105
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java64
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java89
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt4
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt27
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt82
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java1
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt47
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt171
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt31
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt80
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java201
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt)46
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java189
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt26
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt68
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt8
-rw-r--r--media/java/android/media/ImageReader.java12
-rw-r--r--media/java/android/media/ImageUtils.java17
-rw-r--r--media/java/android/media/flags/editing.aconfig7
-rw-r--r--media/java/android/media/tv/TvInputService.java22
-rw-r--r--media/java/android/media/tv/TvInputServiceExtensionManager.java614
-rw-r--r--media/java/android/mtp/OWNERS7
-rw-r--r--media/tests/MtpTests/OWNERS7
-rw-r--r--nfc/api/current.txt9
-rw-r--r--nfc/api/system-current.txt3
-rw-r--r--nfc/java/android/nfc/ComponentNameAndUser.aidl19
-rw-r--r--nfc/java/android/nfc/ComponentNameAndUser.java100
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl5
-rw-r--r--nfc/java/android/nfc/INfcEventListener.aidl11
-rw-r--r--nfc/java/android/nfc/INfcOemExtensionCallback.aidl3
-rw-r--r--nfc/java/android/nfc/NfcOemExtension.java23
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java8
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java109
-rw-r--r--nfc/java/android/nfc/cardemulation/HostApduService.java40
-rw-r--r--packages/SettingsLib/Graph/graph.proto21
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt1
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt41
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt40
-rw-r--r--packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt12
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt4
-rw-r--r--packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt7
-rw-r--r--packages/SystemUI/Android.bp4
-rw-r--r--packages/SystemUI/AndroidManifest.xml1
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig37
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt63
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt14
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt170
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt18
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt182
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt4
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt84
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt51
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt21
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt3
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt20
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt2
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt6
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt35
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt4
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt120
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt18
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt48
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt7
-rw-r--r--packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt2
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt8
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt12
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt91
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt81
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt91
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt81
-rw-r--r--packages/SystemUI/docs/demo_mode.md1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt124
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS1
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt9
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt102
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt143
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt92
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt224
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt335
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt28
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt24
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt52
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt17
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt34
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt21
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt368
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java12
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt123
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt42
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt6
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt76
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt91
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt69
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt80
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java845
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt867
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java111
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt116
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml32
-rw-r--r--packages/SystemUI/res/layout/volume_ringer_drawer.xml2
-rw-r--r--packages/SystemUI/res/values/config.xml4
-rw-r--r--packages/SystemUI/res/values/strings.xml2
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java28
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java12
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java28
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt (renamed from packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt)12
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt135
-rw-r--r--packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt91
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl18
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt130
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt231
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt341
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt31
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt83
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt (renamed from packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt85
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt (renamed from packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java)184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt67
-rw-r--r--packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt110
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt94
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt (renamed from packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt)37
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/domain/interactor/VolumeDialogSliderInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSliderViewBinder.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/VolumeDialogSlidersViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSliderViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/dialog/ui/viewmodel/VolumeDialogViewModel.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt93
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json)0
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json)127
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json)0
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json)127
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json)0
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json)127
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json)0
-rw-r--r--packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json (renamed from packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json)127
-rw-r--r--packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt186
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt41
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt46
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt75
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/GeneralKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt3
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt33
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt22
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt10
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt)5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt (renamed from packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt)29
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt34
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt3
-rw-r--r--packages/overlays/Android.bp1
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java20
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java14
-rw-r--r--ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java161
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java3
-rw-r--r--ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java70
-rw-r--r--ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java63
-rw-r--r--ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java26
-rw-r--r--ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java8
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java13
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java2
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java35
-rw-r--r--ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java5
-rw-r--r--ravenwood/runtime-jni/ravenwood_runtime.cpp8
-rwxr-xr-xravenwood/scripts/run-ravenwood-tests.sh85
-rw-r--r--ravenwood/tests/runtime-test/Android.bp3
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java66
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java33
-rw-r--r--ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java54
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java462
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java213
-rw-r--r--services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java219
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java13
-rw-r--r--services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java27
-rw-r--r--services/companion/java/com/android/server/companion/virtual/CameraAccessController.java13
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java45
-rw-r--r--services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java44
-rw-r--r--services/core/java/com/android/server/SerialService.java21
-rw-r--r--services/core/java/com/android/server/TradeInModeService.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java5
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java28
-rw-r--r--services/core/java/com/android/server/am/OomAdjusterModernImpl.java17
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java1
-rw-r--r--services/core/java/com/android/server/am/flags.aconfig8
-rw-r--r--services/core/java/com/android/server/appbinding/AppBindingService.java3
-rw-r--r--services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java27
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java80
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java21
-rw-r--r--services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java58
-rw-r--r--services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java6
-rw-r--r--services/core/java/com/android/server/display/VirtualDisplayAdapter.java29
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java17
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java7
-rw-r--r--services/core/java/com/android/server/input/AppLaunchShortcutManager.java336
-rw-r--r--services/core/java/com/android/server/input/InputGestureManager.java291
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java12
-rw-r--r--services/core/java/com/android/server/input/KeyGestureController.java329
-rw-r--r--services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java26
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java21
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java33
-rw-r--r--services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java66
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java324
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java69
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java151
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java52
-rw-r--r--services/core/java/com/android/server/integrity/serializer/RuleSerializer.java39
-rw-r--r--services/core/java/com/android/server/media/MediaSession2Record.java5
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecord.java78
-rw-r--r--services/core/java/com/android/server/media/MediaSessionRecordImpl.java4
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java37
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java66
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java12
-rw-r--r--services/core/java/com/android/server/notification/flags.aconfig10
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java4
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java57
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java94
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java26
-rw-r--r--services/core/java/com/android/server/policy/ModifierShortcutManager.java25
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java15
-rw-r--r--services/core/java/com/android/server/power/PowerGroup.java44
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java23
-rw-r--r--services/core/java/com/android/server/power/stats/BatteryStatsImpl.java24
-rw-r--r--services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java24
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java39
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java7
-rw-r--r--services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java10
-rw-r--r--services/core/java/com/android/server/wm/AppCompatController.java16
-rw-r--r--services/core/java/com/android/server/wm/AppCompatUtils.java57
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java6
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java22
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java4
-rw-r--r--services/core/java/com/android/server/wm/InsetsStateController.java4
-rw-r--r--services/core/java/com/android/server/wm/RefreshRatePolicy.java42
-rw-r--r--services/core/java/com/android/server/wm/Session.java2
-rw-r--r--services/core/java/com/android/server/wm/Task.java4
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java9
-rw-r--r--services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java2
-rw-r--r--services/core/java/com/android/server/wm/Transition.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java17
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java6
-rw-r--r--services/core/jni/Android.bp1
-rw-r--r--services/core/jni/com_android_server_SerialService.cpp83
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java1076
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/Owners.java13
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java12
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java24
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java143
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java12
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java4
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/OWNERS2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java31
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java55
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java52
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java122
-rw-r--r--services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java60
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java20
-rw-r--r--services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java5
-rw-r--r--services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java914
-rw-r--r--services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java344
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java8
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java298
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java10
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java52
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java93
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java45
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java95
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java17
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java44
-rw-r--r--services/usb/OWNERS8
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java26
-rw-r--r--telephony/common/com/android/internal/telephony/SmsApplication.java10
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java9
-rw-r--r--telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl (renamed from services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java)24
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java38
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java159
-rw-r--r--telephony/java/com/android/internal/telephony/ITelephony.aidl32
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java156
-rw-r--r--tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt19
-rw-r--r--tests/Input/res/xml/bookmarks.xml60
-rw-r--r--tests/Input/src/com/android/server/input/InputGestureManagerTests.kt3
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt13
-rw-r--r--tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt836
636 files changed, 17631 insertions, 10988 deletions
diff --git a/android-sdk-flags/OWNERS b/android-sdk-flags/OWNERS
new file mode 100644
index 000000000000..01f45dd01e36
--- /dev/null
+++ b/android-sdk-flags/OWNERS
@@ -0,0 +1 @@
+include /SDK_OWNERS
diff --git a/android-sdk-flags/flags.aconfig b/android-sdk-flags/flags.aconfig
index cfe298e187d1..19c7bf674832 100644
--- a/android-sdk-flags/flags.aconfig
+++ b/android-sdk-flags/flags.aconfig
@@ -6,6 +6,7 @@ flag {
namespace: "android_sdk"
description: "Use the new SDK major.minor versioning scheme (e.g. Android 40.1) which replaces the old single-integer scheme (e.g. Android 15)."
bug: "350458259"
+ is_exported: true
# Use is_fixed_read_only because DeviceConfig may not be available when Build.VERSION_CODES is first accessed
is_fixed_read_only: true
diff --git a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
index 8d2d04471f8e..8160ca2eba69 100644
--- a/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/os/LongArrayMultiStateCounterPerfTest.java
@@ -159,9 +159,7 @@ public class LongArrayMultiStateCounterPerfTest {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
long time = 1000;
- LongArrayMultiStateCounter.LongArrayContainer timeInFreq =
- new LongArrayMultiStateCounter.LongArrayContainer(4);
- timeInFreq.setValues(new long[]{100, 200, 300, 400});
+ long[] timeInFreq = {100, 200, 300, 400};
while (state.keepRunning()) {
counter.setState(1, time);
counter.setState(0, time + 1000);
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 3cfddc6d8e2b..fb5ef8771c26 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -173,6 +173,16 @@ public class JobSchedulerImpl extends JobScheduler {
}
@Override
+ @NonNull
+ public int[] getPendingJobReasons(int jobId) {
+ try {
+ return mBinder.getPendingJobReasons(mNamespace, jobId);
+ } catch (RemoteException e) {
+ return new int[] { PENDING_JOB_REASON_UNDEFINED };
+ }
+ }
+
+ @Override
public boolean canRunUserInitiatedJobs() {
try {
return mBinder.canRunUserInitiatedJobs(mContext.getOpPackageName());
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index 416a2d8c0002..21051b520d84 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -39,6 +39,7 @@ interface IJobScheduler {
ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
JobInfo getPendingJob(String namespace, int jobId);
int getPendingJobReason(String namespace, int jobId);
+ int[] getPendingJobReasons(String namespace, int jobId);
boolean canRunUserInitiatedJobs(String packageName);
boolean hasRunUserInitiatedJobsPermission(String packageName, int userId);
List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index ad54cd397413..bfdd15e9b0cd 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -16,6 +16,7 @@
package android.app.job;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -238,6 +239,13 @@ public abstract class JobScheduler {
* to defer this job.
*/
public static final int PENDING_JOB_REASON_USER = 15;
+ /**
+ * The override deadline has not transpired.
+ *
+ * @see JobInfo.Builder#setOverrideDeadline(long)
+ */
+ @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16;
/** @hide */
@IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
@@ -259,6 +267,7 @@ public abstract class JobScheduler {
PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
PENDING_JOB_REASON_QUOTA,
PENDING_JOB_REASON_USER,
+ PENDING_JOB_REASON_CONSTRAINT_DEADLINE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PendingJobReason {
@@ -458,6 +467,10 @@ public abstract class JobScheduler {
/**
* Returns a reason why the job is pending and not currently executing. If there are multiple
* reasons why a job may be pending, this will only return one of them.
+ *
+ * @apiNote
+ * To know all the potential reasons why the job may be pending,
+ * use {@link #getPendingJobReasons(int)} instead.
*/
@PendingJobReason
public int getPendingJobReason(int jobId) {
@@ -465,6 +478,21 @@ public abstract class JobScheduler {
}
/**
+ * Returns potential reasons why the job with the given {@code jobId} may be pending
+ * and not currently executing.
+ *
+ * The returned array will include {@link PendingJobReason reasons} composed of both
+ * explicitly set constraints on the job and implicit constraints imposed by the system.
+ * The results can be used to debug why a given job may not be currently executing.
+ */
+ @FlaggedApi(Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ @NonNull
+ @PendingJobReason
+ public int[] getPendingJobReasons(int jobId) {
+ return new int[] { PENDING_JOB_REASON_UNDEFINED };
+ }
+
+ /**
* Returns {@code true} if the calling app currently holds the
* {@link android.Manifest.permission#RUN_USER_INITIATED_JOBS} permission, allowing it to run
* user-initiated jobs.
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index ba8e3e8b48fc..8f44698ffd8d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -1550,7 +1550,7 @@ class JobConcurrencyManager {
mActivePkgStats.add(
jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
packageStats);
- mService.resetPendingJobReasonCache(jobStatus);
+ mService.resetPendingJobReasonsCache(jobStatus);
}
if (mService.getPendingJobQueue().remove(jobStatus)) {
mService.mJobPackageTracker.noteNonpending(jobStatus);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 4c1951ae9d39..f569388ef3c1 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -460,10 +460,10 @@ public class JobSchedulerService extends com.android.server.SystemService
private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
/**
- * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason.
+ * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reasons.
*/
- @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
- private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache =
+ @GuardedBy("mPendingJobReasonsCache") // Use its own lock to avoid blocking JS processing
+ private final SparseArrayMap<String, SparseArray<int[]>> mPendingJobReasonsCache =
new SparseArrayMap<>();
/**
@@ -2021,139 +2021,123 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- @JobScheduler.PendingJobReason
- private int getPendingJobReason(int uid, String namespace, int jobId) {
- int reason;
+ @NonNull
+ private int[] getPendingJobReasons(int uid, String namespace, int jobId) {
+ int[] reasons;
// Some apps may attempt to query this frequently, so cache the reason under a separate lock
// so that the rest of JS processing isn't negatively impacted.
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
- if (jobIdToReason != null) {
- reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
- if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
- return reason;
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+ if (jobIdToReasons != null) {
+ reasons = jobIdToReasons.get(jobId);
+ if (reasons != null) {
+ return reasons;
}
}
}
synchronized (mLock) {
- reason = getPendingJobReasonLocked(uid, namespace, jobId);
+ reasons = getPendingJobReasonsLocked(uid, namespace, jobId);
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReason("
- + uid + "," + namespace + "," + jobId + ")=" + reason);
+ Slog.v(TAG, "getPendingJobReasons("
+ + uid + "," + namespace + "," + jobId + ")=" + Arrays.toString(reasons));
}
}
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
- if (jobIdToReason == null) {
- jobIdToReason = new SparseIntArray();
- mPendingJobReasonCache.add(uid, namespace, jobIdToReason);
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReasons = mPendingJobReasonsCache.get(uid, namespace);
+ if (jobIdToReasons == null) {
+ jobIdToReasons = new SparseArray<>();
+ mPendingJobReasonsCache.add(uid, namespace, jobIdToReasons);
}
- jobIdToReason.put(jobId, reason);
+ jobIdToReasons.put(jobId, reasons);
}
- return reason;
+ return reasons;
}
@VisibleForTesting
@JobScheduler.PendingJobReason
int getPendingJobReason(JobStatus job) {
- return getPendingJobReason(job.getUid(), job.getNamespace(), job.getJobId());
+ // keep original method to enable unit testing with flags
+ return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId())[0];
+ }
+
+ @VisibleForTesting
+ @NonNull
+ int[] getPendingJobReasons(JobStatus job) {
+ return getPendingJobReasons(job.getUid(), job.getNamespace(), job.getJobId());
}
- @JobScheduler.PendingJobReason
@GuardedBy("mLock")
- private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
+ @NonNull
+ private int[] getPendingJobReasonsLocked(int uid, String namespace, int jobId) {
// Very similar code to isReadyToBeExecutedLocked.
-
- JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+ final JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
if (job == null) {
// Job doesn't exist.
- return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID };
}
-
if (isCurrentlyRunningLocked(job)) {
- return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_EXECUTING };
}
+ final String debugPrefix = "getPendingJobReasonsLocked: " + job.toShortString();
final boolean jobReady = job.isReady();
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " ready=" + jobReady);
+ Slog.v(TAG, debugPrefix + " ready=" + jobReady);
}
-
if (!jobReady) {
- return job.getPendingJobReason();
+ return job.getPendingJobReasons();
}
final boolean userStarted = areUsersStartedLocked(job);
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " userStarted=" + userStarted);
+ Slog.v(TAG, debugPrefix + " userStarted=" + userStarted);
}
if (!userStarted) {
- return JobScheduler.PENDING_JOB_REASON_USER;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_USER };
}
final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " backingUp=" + backingUp);
+ Slog.v(TAG, debugPrefix + " backingUp=" + backingUp);
}
-
if (backingUp) {
// TODO: Should we make a special reason for this?
- return JobScheduler.PENDING_JOB_REASON_APP;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
}
- JobRestriction restriction = checkIfRestricted(job);
+ final JobRestriction restriction = checkIfRestricted(job);
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " restriction=" + restriction);
+ Slog.v(TAG, debugPrefix + " restriction=" + restriction);
}
if (restriction != null) {
- return restriction.getPendingReason();
+ // Currently this will return _DEVICE_STATE because of thermal reasons.
+ // TODO (b/372031023): does it make sense to move this along with the
+ // pendingJobReasons() call above and also get the pending reasons from
+ // all of the restriction controllers?
+ return new int[] { restriction.getPendingReason() };
}
- // The following can be a little more expensive (especially jobActive, since we need to
- // go through the array of all potentially active jobs), so we are doing them
- // later... but still before checking with the package manager!
+ // The following can be a little more expensive, so we are doing it later,
+ // but still before checking with the package manager!
final boolean jobPending = mPendingJobQueue.contains(job);
-
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " pending=" + jobPending);
+ Slog.v(TAG, debugPrefix + " pending=" + jobPending);
}
-
if (jobPending) {
- // We haven't started the job for some reason. Presumably, there are too many jobs
- // running.
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
- }
-
- final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
-
- if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " active=" + jobActive);
- }
- if (jobActive) {
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ // We haven't started the job - presumably, there are too many jobs running.
+ return new int[] { JobScheduler.PENDING_JOB_REASON_DEVICE_STATE };
}
// Validate that the defined package+service is still present & viable.
final boolean componentUsable = isComponentUsable(job);
-
if (DEBUG) {
- Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
- + " componentUsable=" + componentUsable);
+ Slog.v(TAG, debugPrefix + " componentUsable=" + componentUsable);
}
if (!componentUsable) {
- return JobScheduler.PENDING_JOB_REASON_APP;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_APP };
}
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ return new int[] { JobScheduler.PENDING_JOB_REASON_UNDEFINED };
}
private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) {
@@ -2195,15 +2179,16 @@ public class JobSchedulerService extends com.android.server.SystemService
// The app process will be killed soon. There's no point keeping its jobs in
// the pending queue to try and start them.
if (mPendingJobQueue.remove(job)) {
- synchronized (mPendingJobReasonCache) {
- SparseIntArray jobIdToReason = mPendingJobReasonCache.get(
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> jobIdToReason = mPendingJobReasonsCache.get(
job.getUid(), job.getNamespace());
if (jobIdToReason == null) {
- jobIdToReason = new SparseIntArray();
- mPendingJobReasonCache.add(job.getUid(), job.getNamespace(),
+ jobIdToReason = new SparseArray<>();
+ mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(),
jobIdToReason);
}
- jobIdToReason.put(job.getJobId(), JobScheduler.PENDING_JOB_REASON_USER);
+ jobIdToReason.put(job.getJobId(),
+ new int[] { JobScheduler.PENDING_JOB_REASON_USER });
}
}
}
@@ -2229,8 +2214,8 @@ public class JobSchedulerService extends com.android.server.SystemService
synchronized (mLock) {
mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
}
- synchronized (mPendingJobReasonCache) {
- mPendingJobReasonCache.clear();
+ synchronized (mPendingJobReasonsCache) {
+ mPendingJobReasonsCache.clear();
}
}
@@ -2875,7 +2860,7 @@ public class JobSchedulerService extends com.android.server.SystemService
final boolean update = lastJob != null;
mJobs.add(jobStatus);
// Clear potentially cached INVALID_JOB_ID reason.
- resetPendingJobReasonCache(jobStatus);
+ resetPendingJobReasonsCache(jobStatus);
if (mReadyToRock) {
for (int i = 0; i < mControllers.size(); i++) {
StateController controller = mControllers.get(i);
@@ -2897,9 +2882,9 @@ public class JobSchedulerService extends com.android.server.SystemService
// Deal with any remaining work items in the old job.
jobStatus.stopTrackingJobLocked(incomingJob);
- synchronized (mPendingJobReasonCache) {
- SparseIntArray reasonCache =
- mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+ synchronized (mPendingJobReasonsCache) {
+ SparseArray<int[]> reasonCache =
+ mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasonCache != null) {
reasonCache.delete(jobStatus.getJobId());
}
@@ -2927,11 +2912,11 @@ public class JobSchedulerService extends com.android.server.SystemService
return removed;
}
- /** Remove the pending job reason for this job from the cache. */
- void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
- synchronized (mPendingJobReasonCache) {
- final SparseIntArray reasons =
- mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
+ /** Remove the pending job reasons for this job from the cache. */
+ void resetPendingJobReasonsCache(@NonNull JobStatus jobStatus) {
+ synchronized (mPendingJobReasonsCache) {
+ final SparseArray<int[]> reasons =
+ mPendingJobReasonsCache.get(jobStatus.getUid(), jobStatus.getNamespace());
if (reasons != null) {
reasons.delete(jobStatus.getJobId());
}
@@ -3313,18 +3298,18 @@ public class JobSchedulerService extends com.android.server.SystemService
public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
if (changedJobs == null) {
mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
- synchronized (mPendingJobReasonCache) {
- mPendingJobReasonCache.clear();
+ synchronized (mPendingJobReasonsCache) {
+ mPendingJobReasonsCache.clear();
}
} else if (changedJobs.size() > 0) {
synchronized (mLock) {
mChangedJobList.addAll(changedJobs);
}
mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
- synchronized (mPendingJobReasonCache) {
+ synchronized (mPendingJobReasonsCache) {
for (int i = changedJobs.size() - 1; i >= 0; --i) {
final JobStatus job = changedJobs.valueAt(i);
- resetPendingJobReasonCache(job);
+ resetPendingJobReasonsCache(job);
}
}
}
@@ -3893,23 +3878,21 @@ public class JobSchedulerService extends com.android.server.SystemService
// Update the pending reason for any jobs that aren't going to be run.
final int numRunnableJobs = runnableJobs.size();
if (numRunnableJobs > 0 && numRunnableJobs != jobsToRun.size()) {
- synchronized (mPendingJobReasonCache) {
+ synchronized (mPendingJobReasonsCache) {
for (int i = 0; i < numRunnableJobs; ++i) {
final JobStatus job = runnableJobs.get(i);
if (jobsToRun.contains(job)) {
- // We're running this job. Skip updating the pending reason.
- continue;
+ continue; // we're running this job - skip updating the pending reason.
}
- SparseIntArray reasons =
- mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
+ SparseArray<int[]> reasons =
+ mPendingJobReasonsCache.get(job.getUid(), job.getNamespace());
if (reasons == null) {
- reasons = new SparseIntArray();
- mPendingJobReasonCache.add(job.getUid(), job.getNamespace(), reasons);
+ reasons = new SparseArray<>();
+ mPendingJobReasonsCache.add(job.getUid(), job.getNamespace(), reasons);
}
- // We're force batching these jobs, so consider it an optimization
- // policy reason.
- reasons.put(job.getJobId(),
- JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+ // we're force batching these jobs - note it as optimization.
+ reasons.put(job.getJobId(), new int[]
+ { JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION });
}
}
}
@@ -5123,12 +5106,16 @@ public class JobSchedulerService extends com.android.server.SystemService
@Override
public int getPendingJobReason(String namespace, int jobId) throws RemoteException {
- final int uid = Binder.getCallingUid();
+ return getPendingJobReasons(validateNamespace(namespace), jobId)[0];
+ }
+ @Override
+ public int[] getPendingJobReasons(String namespace, int jobId) throws RemoteException {
+ final int uid = Binder.getCallingUid();
final long ident = Binder.clearCallingIdentity();
try {
- return JobSchedulerService.this.getPendingJobReason(
- uid, validateNamespace(namespace), jobId);
+ return JobSchedulerService.this.getPendingJobReasons(
+ uid, validateNamespace(namespace), jobId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -5867,6 +5854,9 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.print(android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND,
android.app.job.Flags.ignoreImportantWhileForeground());
pw.println();
+ pw.print(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API,
+ android.app.job.Flags.getPendingJobReasonsApi());
+ pw.println();
pw.decreaseIndent();
pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 68303e86055c..a4a302450849 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -439,6 +439,9 @@ public final class JobSchedulerShellCommand extends BasicShellCommandHandler {
case android.app.job.Flags.FLAG_IGNORE_IMPORTANT_WHILE_FOREGROUND:
pw.println(android.app.job.Flags.ignoreImportantWhileForeground());
break;
+ case android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API:
+ pw.println(android.app.job.Flags.getPendingJobReasonsApi());
+ break;
default:
pw.println("Unknown flag: " + flagName);
break;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index 1dc5a714337c..58579eb0db47 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -2067,12 +2067,11 @@ public final class JobStatus {
}
/**
- * If {@link #isReady()} returns false, this will return a single reason why the job isn't
- * ready. If {@link #isReady()} returns true, this will return
- * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+ * This will return all potential reasons why the job is pending.
*/
- @JobScheduler.PendingJobReason
- public int getPendingJobReason() {
+ @NonNull
+ public int[] getPendingJobReasons() {
+ final ArrayList<Integer> reasons = new ArrayList<>();
final int unsatisfiedConstraints = ~satisfiedConstraints
& (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
@@ -2084,78 +2083,99 @@ public final class JobStatus {
// (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
// battery saver state.
if (mIsUserBgRestricted) {
- return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION);
+ } else {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
+ }
+ }
+ if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_DEVICE_STATE);
}
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
}
+
if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
}
if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+ }
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
- }
- if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
- }
- if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
- }
- if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
- }
- if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
}
if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
// The developer requested this constraint, so it makes sense to return the
// explicit constraint reason.
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE);
+ } else {
+ // Hard-coding right now since the current dynamic constraint sets don't overlap
+ // TODO: return based on active dynamic constraint sets when they start overlapping
+ if (!reasons.contains(JobScheduler.PENDING_JOB_REASON_APP_STANDBY)) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_APP_STANDBY);
+ }
}
- // Hard-coding right now since the current dynamic constraint sets don't overlap
- // TODO: return based on active dynamic constraint sets when they start overlapping
- return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+ }
+
+ if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY);
+ }
+ if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER);
+ }
+ if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
}
if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH);
}
if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW);
}
if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY);
}
if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
- return JobScheduler.PENDING_JOB_REASON_QUOTA;
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_QUOTA);
}
-
- if (getEffectiveStandbyBucket() == NEVER_INDEX) {
- Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
- // The user hasn't officially launched this app.
- return JobScheduler.PENDING_JOB_REASON_USER;
+ if (android.app.job.Flags.getPendingJobReasonsApi()) {
+ if ((CONSTRAINT_DEADLINE & unsatisfiedConstraints) != 0) {
+ reasons.addLast(JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEADLINE);
+ }
}
- if (serviceProcessName != null) {
- return JobScheduler.PENDING_JOB_REASON_APP;
+
+ if (reasons.isEmpty()) {
+ if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+ Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+ // The user hasn't officially launched this app.
+ reasons.add(JobScheduler.PENDING_JOB_REASON_USER);
+ } else if (serviceProcessName != null) {
+ reasons.add(JobScheduler.PENDING_JOB_REASON_APP);
+ } else {
+ reasons.add(JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+ }
}
- if (!isReady()) {
- Slog.wtf(TAG, "Unknown reason job isn't ready");
+ final int[] reasonsArr = new int[reasons.size()];
+ for (int i = 0; i < reasonsArr.length; i++) {
+ reasonsArr[i] = reasons.get(i);
}
- return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+ return reasonsArr;
}
/** @return whether or not the @param constraint is satisfied */
diff --git a/config/preloaded-classes-denylist b/config/preloaded-classes-denylist
index a413bbd68f60..16f069394639 100644
--- a/config/preloaded-classes-denylist
+++ b/config/preloaded-classes-denylist
@@ -1,13 +1,16 @@
android.content.AsyncTaskLoader$LoadTask
+android.media.MediaCodecInfo$CodecCapabilities$FeatureList
android.net.ConnectivityThread$Singleton
android.os.FileObserver
android.os.NullVibrator
+android.permission.PermissionManager
+android.provider.MediaStore
android.speech.tts.TextToSpeech$Connection$SetupConnectionAsyncTask
+android.view.HdrRenderState
android.widget.Magnifier
+com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+com.android.internal.util.LatencyTracker$SLatencyTrackerHolder
gov.nist.core.net.DefaultNetworkLayer
-android.net.rtp.AudioGroup
-android.net.rtp.AudioStream
-android.net.rtp.RtpStream
java.util.concurrent.ThreadLocalRandom
java.util.ImmutableCollections
-com.android.internal.jank.InteractionJankMonitor$InstanceHolder
+sun.nio.fs.UnixChannelFactory
diff --git a/core/api/current.txt b/core/api/current.txt
index 9bcdcd14aad0..1a53437b3364 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1074,6 +1074,7 @@ package android {
field public static final int layout = 16842994; // 0x10100f2
field public static final int layoutAnimation = 16842988; // 0x10100ec
field public static final int layoutDirection = 16843698; // 0x10103b2
+ field @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public static final int layoutLabel;
field public static final int layoutMode = 16843738; // 0x10103da
field public static final int layout_above = 16843140; // 0x1010184
field public static final int layout_alignBaseline = 16843142; // 0x1010186
@@ -8023,6 +8024,7 @@ package android.app.admin {
field @FlaggedApi("android.view.contentprotection.flags.manage_device_policy_enabled") public static final String CONTENT_PROTECTION_POLICY = "contentProtection";
field public static final String KEYGUARD_DISABLED_FEATURES_POLICY = "keyguardDisabledFeatures";
field public static final String LOCK_TASK_POLICY = "lockTask";
+ field @FlaggedApi("android.app.admin.flags.set_mte_policy_coexistence") public static final String MEMORY_TAGGING_POLICY = "memoryTagging";
field public static final String PACKAGES_SUSPENDED_POLICY = "packagesSuspended";
field public static final String PACKAGE_UNINSTALL_BLOCKED_POLICY = "packageUninstallBlocked";
field public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
@@ -9245,6 +9247,7 @@ package android.app.job {
method @Nullable public String getNamespace();
method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
method public int getPendingJobReason(int);
+ method @FlaggedApi("android.app.job.get_pending_job_reasons_api") @NonNull public int[] getPendingJobReasons(int);
method @NonNull public java.util.Map<java.lang.String,java.util.List<android.app.job.JobInfo>> getPendingJobsInAllNamespaces();
method public abstract int schedule(@NonNull android.app.job.JobInfo);
field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
@@ -9254,6 +9257,7 @@ package android.app.job {
field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+ field @FlaggedApi("android.app.job.get_pending_job_reasons_api") public static final int PENDING_JOB_REASON_CONSTRAINT_DEADLINE = 16; // 0x10
field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
@@ -16416,6 +16420,7 @@ package android.graphics {
field public static final int FLEX_RGBA_8888 = 42; // 0x2a
field public static final int FLEX_RGB_888 = 41; // 0x29
field public static final int HEIC = 1212500294; // 0x48454946
+ field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int HEIC_ULTRAHDR = 4102; // 0x1006
field public static final int JPEG = 256; // 0x100
field public static final int JPEG_R = 4101; // 0x1005
field public static final int NV16 = 16; // 0x10
@@ -18706,6 +18711,7 @@ package android.hardware {
field public static final int DATASPACE_DISPLAY_P3 = 143261696; // 0x88a0000
field public static final int DATASPACE_DYNAMIC_DEPTH = 4098; // 0x1002
field public static final int DATASPACE_HEIF = 4100; // 0x1004
+ field @FlaggedApi("com.android.internal.camera.flags.camera_heif_gainmap") public static final int DATASPACE_HEIF_ULTRAHDR = 4102; // 0x1006
field public static final int DATASPACE_JFIF = 146931712; // 0x8c20000
field public static final int DATASPACE_JPEG_R = 4101; // 0x1005
field public static final int DATASPACE_SCRGB = 411107328; // 0x18810000
@@ -20984,6 +20990,7 @@ package android.inputmethodservice {
method @Deprecated public android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface();
method public android.view.View onCreateInputView();
method protected void onCurrentInputMethodSubtypeChanged(android.view.inputmethod.InputMethodSubtype);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") public void onCustomImeSwitcherButtonRequestedVisible(boolean);
method public void onDisplayCompletions(android.view.inputmethod.CompletionInfo[]);
method public boolean onEvaluateFullscreenMode();
method @CallSuper public boolean onEvaluateInputViewShown();
@@ -52995,7 +53002,7 @@ package android.view {
method public void addOnUnhandledKeyEventListener(android.view.View.OnUnhandledKeyEventListener);
method public void addTouchables(java.util.ArrayList<android.view.View>);
method public android.view.ViewPropertyAnimator animate();
- method public void announceForAccessibility(CharSequence);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public void announceForAccessibility(CharSequence);
method public void autofill(android.view.autofill.AutofillValue);
method public void autofill(@NonNull android.util.SparseArray<android.view.autofill.AutofillValue>);
method protected boolean awakenScrollBars();
@@ -55245,7 +55252,7 @@ package android.view.accessibility {
field public static final int SPEECH_STATE_SPEAKING_END = 2; // 0x2
field public static final int SPEECH_STATE_SPEAKING_START = 1; // 0x1
field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
- field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field @Deprecated @FlaggedApi("android.view.accessibility.deprecate_accessibility_announcement_apis") public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
field public static final int TYPE_ASSIST_READING_CONTEXT = 16777216; // 0x1000000
field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
@@ -56973,6 +56980,9 @@ package android.view.inputmethod {
method public String getExtraValueOf(String);
method public int getIconResId();
method @NonNull public String getLanguageTag();
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutDisplayName(@NonNull android.content.Context, @NonNull android.content.pm.ApplicationInfo);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public CharSequence getLayoutLabelNonLocalized();
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @StringRes public int getLayoutLabelResource();
method @Deprecated @NonNull public String getLocale();
method public String getMode();
method @NonNull public CharSequence getNameOverride();
@@ -56992,6 +57002,8 @@ package android.view.inputmethod {
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAsciiCapable(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setIsAuxiliary(boolean);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLanguageTag(String);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelNonLocalized(@NonNull CharSequence);
+ method @FlaggedApi("android.view.inputmethod.ime_switcher_revamp_api") @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setLayoutLabelResource(@StringRes int);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setOverridesImplicitlyEnabledSubtype(boolean);
method @NonNull public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setPhysicalKeyboardHint(@Nullable android.icu.util.ULocale, @NonNull String);
method public android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder setSubtypeExtraValue(String);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 02cd00d0f86a..26cb5621bab0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3558,10 +3558,12 @@ package android.companion.virtual {
method @Deprecated public int getDefaultActivityPolicy();
method @Deprecated public int getDefaultNavigationPolicy();
method public int getDevicePolicy(int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getDimDuration();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent();
method public int getLockState();
method @Nullable public String getName();
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public java.time.Duration getScreenOffTimeout();
method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts();
method @NonNull public java.util.List<android.companion.virtual.sensor.VirtualSensorConfig> getVirtualSensorConfigs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -3579,6 +3581,7 @@ package android.companion.virtual {
field @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6; // 0x6
field @FlaggedApi("android.companion.virtual.flags.virtual_camera") public static final int POLICY_TYPE_CAMERA = 5; // 0x5
field @FlaggedApi("android.companion.virtual.flags.cross_device_clipboard") public static final int POLICY_TYPE_CLIPBOARD = 4; // 0x4
+ field @FlaggedApi("android.companion.virtualdevice.flags.default_device_camera_access_policy") public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7; // 0x7
field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
}
@@ -3594,10 +3597,12 @@ package android.companion.virtual {
method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDimDuration(@NonNull java.time.Duration);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName);
method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setScreenOffTimeout(@NonNull java.time.Duration);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorCallback);
method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setVirtualSensorDirectChannelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.sensor.VirtualSensorDirectChannelCallback);
@@ -5288,13 +5293,19 @@ package android.hardware.display {
field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
}
+ public abstract static class VirtualDisplay.Callback {
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") public void onRequestedBrightnessChanged(@FloatRange(from=0.0f, to=1.0f) float);
+ }
+
public final class VirtualDisplayConfig implements android.os.Parcelable {
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @FloatRange(from=0.0f, to=1.0f) public float getDefaultBrightness();
method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @Nullable public android.view.DisplayCutout getDisplayCutout();
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") public boolean isHomeSupported();
method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") public boolean isIgnoreActivitySizeRestrictions();
}
public static final class VirtualDisplayConfig.Builder {
+ method @FlaggedApi("android.companion.virtualdevice.flags.device_aware_display_power") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDefaultBrightness(@FloatRange(from=0.0f, to=1.0f) float);
method @FlaggedApi("android.companion.virtualdevice.flags.virtual_display_insets") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setDisplayCutout(@Nullable android.view.DisplayCutout);
method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setHomeSupported(boolean);
method @FlaggedApi("com.android.window.flags.vdm_force_app_universal_resizable_api") @NonNull public android.hardware.display.VirtualDisplayConfig.Builder setIgnoreActivitySizeRestrictions(boolean);
@@ -7022,7 +7033,7 @@ package android.hardware.soundtrigger {
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAllowMultipleTriggers(boolean);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setAudioCapabilities(int);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setCaptureRequested(boolean);
- method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@Nullable byte[]);
+ method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setData(@NonNull byte[]);
method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig.Builder setKeyphrases(@NonNull java.util.Collection<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>);
}
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 36fc65a76d53..b447897733e1 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2764,14 +2764,19 @@ public class ActivityManager {
/**
* Information of organized child tasks.
*
+ * @deprecated No longer used
* @hide
*/
+ @Deprecated
public ArrayList<RecentTaskInfo> childrenTaskInfos = new ArrayList<>();
/**
* Information about the last snapshot taken for this task.
+ *
+ * @deprecated No longer used
* @hide
*/
+ @Deprecated
public PersistedTaskSnapshotData lastSnapshotData = new PersistedTaskSnapshotData();
public RecentTaskInfo() {
@@ -2793,7 +2798,7 @@ public class ActivityManager {
lastSnapshotData.taskSize = source.readTypedObject(Point.CREATOR);
lastSnapshotData.contentInsets = source.readTypedObject(Rect.CREATOR);
lastSnapshotData.bufferSize = source.readTypedObject(Point.CREATOR);
- super.readFromParcel(source);
+ super.readTaskFromParcel(source);
}
@Override
@@ -2804,7 +2809,7 @@ public class ActivityManager {
dest.writeTypedObject(lastSnapshotData.taskSize, flags);
dest.writeTypedObject(lastSnapshotData.contentInsets, flags);
dest.writeTypedObject(lastSnapshotData.bufferSize, flags);
- super.writeToParcel(dest, flags);
+ super.writeTaskToParcel(dest, flags);
}
public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR
@@ -2988,13 +2993,13 @@ public class ActivityManager {
public void readFromParcel(Parcel source) {
id = source.readInt();
- super.readFromParcel(source);
+ super.readTaskFromParcel(source);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
- super.writeToParcel(dest, flags);
+ super.writeTaskToParcel(dest, flags);
}
public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 799df1f9227a..16dcf2ad7e45 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -534,10 +534,9 @@ public class ActivityTaskManager {
dest.writeIntArray(childTaskUserIds);
dest.writeInt(visible ? 1 : 0);
dest.writeInt(position);
- super.writeToParcel(dest, flags);
+ super.writeTaskToParcel(dest, flags);
}
- @Override
void readFromParcel(Parcel source) {
bounds = source.readTypedObject(Rect.CREATOR);
childTaskIds = source.createIntArray();
@@ -546,7 +545,7 @@ public class ActivityTaskManager {
childTaskUserIds = source.createIntArray();
visible = source.readInt() > 0;
position = source.readInt();
- super.readFromParcel(source);
+ super.readTaskFromParcel(source);
}
public static final @NonNull Creator<RootTaskInfo> CREATOR = new Creator<>() {
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ca98da76b78f..60b8f80d8f2d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3100,6 +3100,19 @@ public final class ActivityThread extends ClientTransactionHandler
mResourcesManager = ResourcesManager.getInstance();
}
+ /**
+ * Creates and initialize a new system activity thread, to be used for testing. This does not
+ * call {@link #attach}, so it does not modify static state.
+ */
+ @VisibleForTesting
+ @NonNull
+ public static ActivityThread createSystemActivityThreadForTesting() {
+ final var thread = new ActivityThread();
+ thread.mSystemThread = true;
+ initializeSystemThread(thread);
+ return thread;
+ }
+
@UnsupportedAppUsage
public ApplicationThread getApplicationThread()
{
@@ -6806,6 +6819,16 @@ public final class ActivityThread extends ClientTransactionHandler
LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
resApk.updateApplicationInfo(ai, oldPaths);
}
+ if (android.content.res.Flags.systemContextHandleAppInfoChanged() && mSystemThread) {
+ final var systemContext = getSystemContext();
+ if (systemContext.getPackageName().equals(ai.packageName)) {
+ // The system package is not tracked directly, but still needs to receive updates to
+ // its application info.
+ final ArrayList<String> oldPaths = new ArrayList<>();
+ LoadedApk.makePaths(this, systemContext.getApplicationInfo(), oldPaths);
+ systemContext.mPackageInfo.updateApplicationInfo(ai, oldPaths);
+ }
+ }
ResourcesImpl beforeImpl = getApplication().getResources().getImpl();
@@ -8560,17 +8583,7 @@ public final class ActivityThread extends ClientTransactionHandler
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process",
UserHandle.myUserId());
- try {
- mInstrumentation = new Instrumentation();
- mInstrumentation.basicInit(this);
- ContextImpl context = ContextImpl.createAppContext(
- this, getSystemContext().mPackageInfo);
- mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
- mInitialApplication.onCreate();
- } catch (Exception e) {
- throw new RuntimeException(
- "Unable to instantiate Application():" + e.toString(), e);
- }
+ initializeSystemThread(this);
}
ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
@@ -8595,6 +8608,28 @@ public final class ActivityThread extends ClientTransactionHandler
ViewRootImpl.addConfigCallback(configChangedCallback);
}
+ /**
+ * Initializes the given system activity thread, setting up its instrumentation and initial
+ * application. This only has an effect if the given thread is a {@link #mSystemThread}.
+ *
+ * @param thread the given system activity thread to initialize.
+ */
+ private static void initializeSystemThread(@NonNull ActivityThread thread) {
+ if (!thread.mSystemThread) {
+ return;
+ }
+ try {
+ thread.mInstrumentation = new Instrumentation();
+ thread.mInstrumentation.basicInit(thread);
+ ContextImpl context = ContextImpl.createAppContext(
+ thread, thread.getSystemContext().mPackageInfo);
+ thread.mInitialApplication = context.mPackageInfo.makeApplicationInner(true, null);
+ thread.mInitialApplication.onCreate();
+ } catch (Exception e) {
+ throw new RuntimeException("Unable to instantiate Application():" + e, e);
+ }
+ }
+
@UnsupportedAppUsage
public static ActivityThread systemMain() {
ThreadedRenderer.initForSystemProcess();
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 8370c2e522f3..68794588afc5 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -167,10 +167,11 @@ public class AppCompatTaskInfo implements Parcelable {
}
/**
- * @return {@code true} if top activity is pillarboxed.
+ * @return {@code true} if the top activity bounds are letterboxed with width <= height.
*/
- public boolean isTopActivityPillarboxed() {
- return topActivityLetterboxWidth < topActivityLetterboxHeight;
+ public boolean isTopActivityPillarboxShaped() {
+ return isTopActivityLetterboxed()
+ && topActivityLetterboxWidth <= topActivityLetterboxHeight;
}
/**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 2b0e86c3205c..4c860fac9871 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -63,6 +63,7 @@ import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.PackageTagsList;
import android.os.Parcel;
@@ -78,12 +79,14 @@ import android.permission.PermissionGroupUsage;
import android.permission.PermissionUsageHelper;
import android.permission.flags.Flags;
import android.provider.DeviceConfig;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.LongSparseArray;
import android.util.LongSparseLongArray;
import android.util.Pools;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
@@ -910,159 +913,157 @@ public class AppOpsManager {
/** @hide No operation specified. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int OP_NONE = AppProtoEnums.APP_OP_NONE;
+ public static final int OP_NONE = AppOpEnums.APP_OP_NONE;
/** @hide Access to coarse location information. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_COARSE_LOCATION = AppProtoEnums.APP_OP_COARSE_LOCATION;
+ public static final int OP_COARSE_LOCATION = AppOpEnums.APP_OP_COARSE_LOCATION;
/** @hide Access to fine location information. */
@UnsupportedAppUsage
- public static final int OP_FINE_LOCATION = AppProtoEnums.APP_OP_FINE_LOCATION;
+ public static final int OP_FINE_LOCATION = AppOpEnums.APP_OP_FINE_LOCATION;
/** @hide Causing GPS to run. */
@UnsupportedAppUsage
- public static final int OP_GPS = AppProtoEnums.APP_OP_GPS;
+ public static final int OP_GPS = AppOpEnums.APP_OP_GPS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_VIBRATE = AppProtoEnums.APP_OP_VIBRATE;
+ public static final int OP_VIBRATE = AppOpEnums.APP_OP_VIBRATE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CONTACTS = AppProtoEnums.APP_OP_READ_CONTACTS;
+ public static final int OP_READ_CONTACTS = AppOpEnums.APP_OP_READ_CONTACTS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CONTACTS = AppProtoEnums.APP_OP_WRITE_CONTACTS;
+ public static final int OP_WRITE_CONTACTS = AppOpEnums.APP_OP_WRITE_CONTACTS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CALL_LOG = AppProtoEnums.APP_OP_READ_CALL_LOG;
+ public static final int OP_READ_CALL_LOG = AppOpEnums.APP_OP_READ_CALL_LOG;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CALL_LOG = AppProtoEnums.APP_OP_WRITE_CALL_LOG;
+ public static final int OP_WRITE_CALL_LOG = AppOpEnums.APP_OP_WRITE_CALL_LOG;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CALENDAR = AppProtoEnums.APP_OP_READ_CALENDAR;
+ public static final int OP_READ_CALENDAR = AppOpEnums.APP_OP_READ_CALENDAR;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CALENDAR = AppProtoEnums.APP_OP_WRITE_CALENDAR;
+ public static final int OP_WRITE_CALENDAR = AppOpEnums.APP_OP_WRITE_CALENDAR;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WIFI_SCAN = AppProtoEnums.APP_OP_WIFI_SCAN;
+ public static final int OP_WIFI_SCAN = AppOpEnums.APP_OP_WIFI_SCAN;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_POST_NOTIFICATION = AppProtoEnums.APP_OP_POST_NOTIFICATION;
+ public static final int OP_POST_NOTIFICATION = AppOpEnums.APP_OP_POST_NOTIFICATION;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_NEIGHBORING_CELLS = AppProtoEnums.APP_OP_NEIGHBORING_CELLS;
+ public static final int OP_NEIGHBORING_CELLS = AppOpEnums.APP_OP_NEIGHBORING_CELLS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_CALL_PHONE = AppProtoEnums.APP_OP_CALL_PHONE;
+ public static final int OP_CALL_PHONE = AppOpEnums.APP_OP_CALL_PHONE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_SMS = AppProtoEnums.APP_OP_READ_SMS;
+ public static final int OP_READ_SMS = AppOpEnums.APP_OP_READ_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_SMS = AppProtoEnums.APP_OP_WRITE_SMS;
+ public static final int OP_WRITE_SMS = AppOpEnums.APP_OP_WRITE_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_SMS = AppProtoEnums.APP_OP_RECEIVE_SMS;
+ public static final int OP_RECEIVE_SMS = AppOpEnums.APP_OP_RECEIVE_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_EMERGECY_SMS =
- AppProtoEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
+ public static final int OP_RECEIVE_EMERGECY_SMS = AppOpEnums.APP_OP_RECEIVE_EMERGENCY_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_MMS = AppProtoEnums.APP_OP_RECEIVE_MMS;
+ public static final int OP_RECEIVE_MMS = AppOpEnums.APP_OP_RECEIVE_MMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_RECEIVE_WAP_PUSH = AppProtoEnums.APP_OP_RECEIVE_WAP_PUSH;
+ public static final int OP_RECEIVE_WAP_PUSH = AppOpEnums.APP_OP_RECEIVE_WAP_PUSH;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_SEND_SMS = AppProtoEnums.APP_OP_SEND_SMS;
+ public static final int OP_SEND_SMS = AppOpEnums.APP_OP_SEND_SMS;
/** @hide */
- public static final int OP_MANAGE_ONGOING_CALLS = AppProtoEnums.APP_OP_MANAGE_ONGOING_CALLS;
+ public static final int OP_MANAGE_ONGOING_CALLS = AppOpEnums.APP_OP_MANAGE_ONGOING_CALLS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_ICC_SMS = AppProtoEnums.APP_OP_READ_ICC_SMS;
+ public static final int OP_READ_ICC_SMS = AppOpEnums.APP_OP_READ_ICC_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_ICC_SMS = AppProtoEnums.APP_OP_WRITE_ICC_SMS;
+ public static final int OP_WRITE_ICC_SMS = AppOpEnums.APP_OP_WRITE_ICC_SMS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_SETTINGS = AppProtoEnums.APP_OP_WRITE_SETTINGS;
+ public static final int OP_WRITE_SETTINGS = AppOpEnums.APP_OP_WRITE_SETTINGS;
/** @hide Required to draw on top of other apps. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_SYSTEM_ALERT_WINDOW = AppProtoEnums.APP_OP_SYSTEM_ALERT_WINDOW;
+ public static final int OP_SYSTEM_ALERT_WINDOW = AppOpEnums.APP_OP_SYSTEM_ALERT_WINDOW;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_ACCESS_NOTIFICATIONS =
- AppProtoEnums.APP_OP_ACCESS_NOTIFICATIONS;
+ public static final int OP_ACCESS_NOTIFICATIONS = AppOpEnums.APP_OP_ACCESS_NOTIFICATIONS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_CAMERA = AppProtoEnums.APP_OP_CAMERA;
+ public static final int OP_CAMERA = AppOpEnums.APP_OP_CAMERA;
/** @hide */
@UnsupportedAppUsage
@TestApi
- public static final int OP_RECORD_AUDIO = AppProtoEnums.APP_OP_RECORD_AUDIO;
+ public static final int OP_RECORD_AUDIO = AppOpEnums.APP_OP_RECORD_AUDIO;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_PLAY_AUDIO = AppProtoEnums.APP_OP_PLAY_AUDIO;
+ public static final int OP_PLAY_AUDIO = AppOpEnums.APP_OP_PLAY_AUDIO;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_READ_CLIPBOARD = AppProtoEnums.APP_OP_READ_CLIPBOARD;
+ public static final int OP_READ_CLIPBOARD = AppOpEnums.APP_OP_READ_CLIPBOARD;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WRITE_CLIPBOARD = AppProtoEnums.APP_OP_WRITE_CLIPBOARD;
+ public static final int OP_WRITE_CLIPBOARD = AppOpEnums.APP_OP_WRITE_CLIPBOARD;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TAKE_MEDIA_BUTTONS = AppProtoEnums.APP_OP_TAKE_MEDIA_BUTTONS;
+ public static final int OP_TAKE_MEDIA_BUTTONS = AppOpEnums.APP_OP_TAKE_MEDIA_BUTTONS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TAKE_AUDIO_FOCUS = AppProtoEnums.APP_OP_TAKE_AUDIO_FOCUS;
+ public static final int OP_TAKE_AUDIO_FOCUS = AppOpEnums.APP_OP_TAKE_AUDIO_FOCUS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_MASTER_VOLUME = AppProtoEnums.APP_OP_AUDIO_MASTER_VOLUME;
+ public static final int OP_AUDIO_MASTER_VOLUME = AppOpEnums.APP_OP_AUDIO_MASTER_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_VOICE_VOLUME = AppProtoEnums.APP_OP_AUDIO_VOICE_VOLUME;
+ public static final int OP_AUDIO_VOICE_VOLUME = AppOpEnums.APP_OP_AUDIO_VOICE_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_RING_VOLUME = AppProtoEnums.APP_OP_AUDIO_RING_VOLUME;
+ public static final int OP_AUDIO_RING_VOLUME = AppOpEnums.APP_OP_AUDIO_RING_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_MEDIA_VOLUME = AppProtoEnums.APP_OP_AUDIO_MEDIA_VOLUME;
+ public static final int OP_AUDIO_MEDIA_VOLUME = AppOpEnums.APP_OP_AUDIO_MEDIA_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_AUDIO_ALARM_VOLUME = AppProtoEnums.APP_OP_AUDIO_ALARM_VOLUME;
+ public static final int OP_AUDIO_ALARM_VOLUME = AppOpEnums.APP_OP_AUDIO_ALARM_VOLUME;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_NOTIFICATION_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_NOTIFICATION_VOLUME;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_BLUETOOTH_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_BLUETOOTH_VOLUME;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_WAKE_LOCK = AppProtoEnums.APP_OP_WAKE_LOCK;
+ public static final int OP_WAKE_LOCK = AppOpEnums.APP_OP_WAKE_LOCK;
/** @hide Continually monitoring location data. */
@UnsupportedAppUsage
public static final int OP_MONITOR_LOCATION =
- AppProtoEnums.APP_OP_MONITOR_LOCATION;
+ AppOpEnums.APP_OP_MONITOR_LOCATION;
/** @hide Continually monitoring location data with a relatively high power request. */
@UnsupportedAppUsage
public static final int OP_MONITOR_HIGH_POWER_LOCATION =
- AppProtoEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
+ AppOpEnums.APP_OP_MONITOR_HIGH_POWER_LOCATION;
/** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
@UnsupportedAppUsage
- public static final int OP_GET_USAGE_STATS = AppProtoEnums.APP_OP_GET_USAGE_STATS;
+ public static final int OP_GET_USAGE_STATS = AppOpEnums.APP_OP_GET_USAGE_STATS;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_MUTE_MICROPHONE = AppProtoEnums.APP_OP_MUTE_MICROPHONE;
+ public static final int OP_MUTE_MICROPHONE = AppOpEnums.APP_OP_MUTE_MICROPHONE;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_TOAST_WINDOW = AppProtoEnums.APP_OP_TOAST_WINDOW;
+ public static final int OP_TOAST_WINDOW = AppOpEnums.APP_OP_TOAST_WINDOW;
/** @hide Capture the device's display contents and/or audio */
@UnsupportedAppUsage
- public static final int OP_PROJECT_MEDIA = AppProtoEnums.APP_OP_PROJECT_MEDIA;
+ public static final int OP_PROJECT_MEDIA = AppOpEnums.APP_OP_PROJECT_MEDIA;
/**
* Start (without additional user intervention) a VPN connection, as used by {@link
* android.net.VpnService} along with as Platform VPN connections, as used by {@link
@@ -1075,146 +1076,141 @@ public class AppOpsManager {
* @hide
*/
@UnsupportedAppUsage
- public static final int OP_ACTIVATE_VPN = AppProtoEnums.APP_OP_ACTIVATE_VPN;
+ public static final int OP_ACTIVATE_VPN = AppOpEnums.APP_OP_ACTIVATE_VPN;
/** @hide Access the WallpaperManagerAPI to write wallpapers. */
@UnsupportedAppUsage
- public static final int OP_WRITE_WALLPAPER = AppProtoEnums.APP_OP_WRITE_WALLPAPER;
+ public static final int OP_WRITE_WALLPAPER = AppOpEnums.APP_OP_WRITE_WALLPAPER;
/** @hide Received the assist structure from an app. */
@UnsupportedAppUsage
- public static final int OP_ASSIST_STRUCTURE = AppProtoEnums.APP_OP_ASSIST_STRUCTURE;
+ public static final int OP_ASSIST_STRUCTURE = AppOpEnums.APP_OP_ASSIST_STRUCTURE;
/** @hide Received a screenshot from assist. */
@UnsupportedAppUsage
- public static final int OP_ASSIST_SCREENSHOT = AppProtoEnums.APP_OP_ASSIST_SCREENSHOT;
+ public static final int OP_ASSIST_SCREENSHOT = AppOpEnums.APP_OP_ASSIST_SCREENSHOT;
/** @hide Read the phone state. */
@UnsupportedAppUsage
- public static final int OP_READ_PHONE_STATE = AppProtoEnums.APP_OP_READ_PHONE_STATE;
+ public static final int OP_READ_PHONE_STATE = AppOpEnums.APP_OP_READ_PHONE_STATE;
/** @hide Add voicemail messages to the voicemail content provider. */
@UnsupportedAppUsage
- public static final int OP_ADD_VOICEMAIL = AppProtoEnums.APP_OP_ADD_VOICEMAIL;
+ public static final int OP_ADD_VOICEMAIL = AppOpEnums.APP_OP_ADD_VOICEMAIL;
/** @hide Access APIs for SIP calling over VOIP or WiFi. */
@UnsupportedAppUsage
- public static final int OP_USE_SIP = AppProtoEnums.APP_OP_USE_SIP;
+ public static final int OP_USE_SIP = AppOpEnums.APP_OP_USE_SIP;
/** @hide Intercept outgoing calls. */
@UnsupportedAppUsage
- public static final int OP_PROCESS_OUTGOING_CALLS =
- AppProtoEnums.APP_OP_PROCESS_OUTGOING_CALLS;
+ public static final int OP_PROCESS_OUTGOING_CALLS = AppOpEnums.APP_OP_PROCESS_OUTGOING_CALLS;
/** @hide User the fingerprint API. */
@UnsupportedAppUsage
- public static final int OP_USE_FINGERPRINT = AppProtoEnums.APP_OP_USE_FINGERPRINT;
+ public static final int OP_USE_FINGERPRINT = AppOpEnums.APP_OP_USE_FINGERPRINT;
/** @hide Access to body sensors such as heart rate, etc. */
@UnsupportedAppUsage
- public static final int OP_BODY_SENSORS = AppProtoEnums.APP_OP_BODY_SENSORS;
+ public static final int OP_BODY_SENSORS = AppOpEnums.APP_OP_BODY_SENSORS;
/** @hide Read previously received cell broadcast messages. */
@UnsupportedAppUsage
- public static final int OP_READ_CELL_BROADCASTS = AppProtoEnums.APP_OP_READ_CELL_BROADCASTS;
+ public static final int OP_READ_CELL_BROADCASTS = AppOpEnums.APP_OP_READ_CELL_BROADCASTS;
/** @hide Inject mock location into the system. */
@UnsupportedAppUsage
- public static final int OP_MOCK_LOCATION = AppProtoEnums.APP_OP_MOCK_LOCATION;
+ public static final int OP_MOCK_LOCATION = AppOpEnums.APP_OP_MOCK_LOCATION;
/** @hide Read external storage. */
@UnsupportedAppUsage
- public static final int OP_READ_EXTERNAL_STORAGE = AppProtoEnums.APP_OP_READ_EXTERNAL_STORAGE;
+ public static final int OP_READ_EXTERNAL_STORAGE = AppOpEnums.APP_OP_READ_EXTERNAL_STORAGE;
/** @hide Write external storage. */
@UnsupportedAppUsage
- public static final int OP_WRITE_EXTERNAL_STORAGE =
- AppProtoEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
+ public static final int OP_WRITE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_WRITE_EXTERNAL_STORAGE;
/** @hide Turned on the screen. */
@UnsupportedAppUsage
- public static final int OP_TURN_SCREEN_ON = AppProtoEnums.APP_OP_TURN_SCREEN_ON;
+ public static final int OP_TURN_SCREEN_ON = AppOpEnums.APP_OP_TURN_SCREEN_ON;
/** @hide Get device accounts. */
@UnsupportedAppUsage
- public static final int OP_GET_ACCOUNTS = AppProtoEnums.APP_OP_GET_ACCOUNTS;
+ public static final int OP_GET_ACCOUNTS = AppOpEnums.APP_OP_GET_ACCOUNTS;
/** @hide Control whether an application is allowed to run in the background. */
@UnsupportedAppUsage
- public static final int OP_RUN_IN_BACKGROUND =
- AppProtoEnums.APP_OP_RUN_IN_BACKGROUND;
+ public static final int OP_RUN_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_IN_BACKGROUND;
/** @hide */
@UnsupportedAppUsage
public static final int OP_AUDIO_ACCESSIBILITY_VOLUME =
- AppProtoEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
+ AppOpEnums.APP_OP_AUDIO_ACCESSIBILITY_VOLUME;
/** @hide Read the phone number. */
@UnsupportedAppUsage
- public static final int OP_READ_PHONE_NUMBERS = AppProtoEnums.APP_OP_READ_PHONE_NUMBERS;
+ public static final int OP_READ_PHONE_NUMBERS = AppOpEnums.APP_OP_READ_PHONE_NUMBERS;
/** @hide Request package installs through package installer */
@UnsupportedAppUsage
public static final int OP_REQUEST_INSTALL_PACKAGES =
- AppProtoEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
+ AppOpEnums.APP_OP_REQUEST_INSTALL_PACKAGES;
/** @hide Enter picture-in-picture. */
@UnsupportedAppUsage
- public static final int OP_PICTURE_IN_PICTURE = AppProtoEnums.APP_OP_PICTURE_IN_PICTURE;
+ public static final int OP_PICTURE_IN_PICTURE = AppOpEnums.APP_OP_PICTURE_IN_PICTURE;
/** @hide Instant app start foreground service. */
@UnsupportedAppUsage
public static final int OP_INSTANT_APP_START_FOREGROUND =
- AppProtoEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
+ AppOpEnums.APP_OP_INSTANT_APP_START_FOREGROUND;
/** @hide Answer incoming phone calls */
@UnsupportedAppUsage
- public static final int OP_ANSWER_PHONE_CALLS = AppProtoEnums.APP_OP_ANSWER_PHONE_CALLS;
+ public static final int OP_ANSWER_PHONE_CALLS = AppOpEnums.APP_OP_ANSWER_PHONE_CALLS;
/** @hide Run jobs when in background */
@UnsupportedAppUsage
- public static final int OP_RUN_ANY_IN_BACKGROUND = AppProtoEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
+ public static final int OP_RUN_ANY_IN_BACKGROUND = AppOpEnums.APP_OP_RUN_ANY_IN_BACKGROUND;
/** @hide Change Wi-Fi connectivity state */
@UnsupportedAppUsage
- public static final int OP_CHANGE_WIFI_STATE = AppProtoEnums.APP_OP_CHANGE_WIFI_STATE;
+ public static final int OP_CHANGE_WIFI_STATE = AppOpEnums.APP_OP_CHANGE_WIFI_STATE;
/** @hide Request package deletion through package installer */
@UnsupportedAppUsage
- public static final int OP_REQUEST_DELETE_PACKAGES =
- AppProtoEnums.APP_OP_REQUEST_DELETE_PACKAGES;
+ public static final int OP_REQUEST_DELETE_PACKAGES = AppOpEnums.APP_OP_REQUEST_DELETE_PACKAGES;
/** @hide Bind an accessibility service. */
@UnsupportedAppUsage
public static final int OP_BIND_ACCESSIBILITY_SERVICE =
- AppProtoEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
+ AppOpEnums.APP_OP_BIND_ACCESSIBILITY_SERVICE;
/** @hide Continue handover of a call from another app */
@UnsupportedAppUsage
- public static final int OP_ACCEPT_HANDOVER = AppProtoEnums.APP_OP_ACCEPT_HANDOVER;
+ public static final int OP_ACCEPT_HANDOVER = AppOpEnums.APP_OP_ACCEPT_HANDOVER;
/** @hide Create and Manage IPsec Tunnels */
@UnsupportedAppUsage
- public static final int OP_MANAGE_IPSEC_TUNNELS = AppProtoEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
+ public static final int OP_MANAGE_IPSEC_TUNNELS = AppOpEnums.APP_OP_MANAGE_IPSEC_TUNNELS;
/** @hide Any app start foreground service. */
@UnsupportedAppUsage
@TestApi
- public static final int OP_START_FOREGROUND = AppProtoEnums.APP_OP_START_FOREGROUND;
+ public static final int OP_START_FOREGROUND = AppOpEnums.APP_OP_START_FOREGROUND;
/** @hide */
@UnsupportedAppUsage
- public static final int OP_BLUETOOTH_SCAN = AppProtoEnums.APP_OP_BLUETOOTH_SCAN;
+ public static final int OP_BLUETOOTH_SCAN = AppOpEnums.APP_OP_BLUETOOTH_SCAN;
/** @hide */
- public static final int OP_BLUETOOTH_CONNECT = AppProtoEnums.APP_OP_BLUETOOTH_CONNECT;
+ public static final int OP_BLUETOOTH_CONNECT = AppOpEnums.APP_OP_BLUETOOTH_CONNECT;
/** @hide */
- public static final int OP_BLUETOOTH_ADVERTISE = AppProtoEnums.APP_OP_BLUETOOTH_ADVERTISE;
+ public static final int OP_BLUETOOTH_ADVERTISE = AppOpEnums.APP_OP_BLUETOOTH_ADVERTISE;
/** @hide Use the BiometricPrompt/BiometricManager APIs. */
- public static final int OP_USE_BIOMETRIC = AppProtoEnums.APP_OP_USE_BIOMETRIC;
+ public static final int OP_USE_BIOMETRIC = AppOpEnums.APP_OP_USE_BIOMETRIC;
/** @hide Physical activity recognition. */
- public static final int OP_ACTIVITY_RECOGNITION = AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION;
+ public static final int OP_ACTIVITY_RECOGNITION = AppOpEnums.APP_OP_ACTIVITY_RECOGNITION;
/** @hide Financial app sms read. */
public static final int OP_SMS_FINANCIAL_TRANSACTIONS =
- AppProtoEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
+ AppOpEnums.APP_OP_SMS_FINANCIAL_TRANSACTIONS;
/** @hide Read media of audio type. */
- public static final int OP_READ_MEDIA_AUDIO = AppProtoEnums.APP_OP_READ_MEDIA_AUDIO;
+ public static final int OP_READ_MEDIA_AUDIO = AppOpEnums.APP_OP_READ_MEDIA_AUDIO;
/** @hide Write media of audio type. */
- public static final int OP_WRITE_MEDIA_AUDIO = AppProtoEnums.APP_OP_WRITE_MEDIA_AUDIO;
+ public static final int OP_WRITE_MEDIA_AUDIO = AppOpEnums.APP_OP_WRITE_MEDIA_AUDIO;
/** @hide Read media of video type. */
- public static final int OP_READ_MEDIA_VIDEO = AppProtoEnums.APP_OP_READ_MEDIA_VIDEO;
+ public static final int OP_READ_MEDIA_VIDEO = AppOpEnums.APP_OP_READ_MEDIA_VIDEO;
/** @hide Write media of video type. */
- public static final int OP_WRITE_MEDIA_VIDEO = AppProtoEnums.APP_OP_WRITE_MEDIA_VIDEO;
+ public static final int OP_WRITE_MEDIA_VIDEO = AppOpEnums.APP_OP_WRITE_MEDIA_VIDEO;
/** @hide Read media of image type. */
- public static final int OP_READ_MEDIA_IMAGES = AppProtoEnums.APP_OP_READ_MEDIA_IMAGES;
+ public static final int OP_READ_MEDIA_IMAGES = AppOpEnums.APP_OP_READ_MEDIA_IMAGES;
/** @hide Write media of image type. */
- public static final int OP_WRITE_MEDIA_IMAGES = AppProtoEnums.APP_OP_WRITE_MEDIA_IMAGES;
+ public static final int OP_WRITE_MEDIA_IMAGES = AppOpEnums.APP_OP_WRITE_MEDIA_IMAGES;
/** @hide Has a legacy (non-isolated) view of storage. */
- public static final int OP_LEGACY_STORAGE = AppProtoEnums.APP_OP_LEGACY_STORAGE;
+ public static final int OP_LEGACY_STORAGE = AppOpEnums.APP_OP_LEGACY_STORAGE;
/** @hide Accessing accessibility features */
- public static final int OP_ACCESS_ACCESSIBILITY = AppProtoEnums.APP_OP_ACCESS_ACCESSIBILITY;
+ public static final int OP_ACCESS_ACCESSIBILITY = AppOpEnums.APP_OP_ACCESS_ACCESSIBILITY;
/** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
public static final int OP_READ_DEVICE_IDENTIFIERS =
- AppProtoEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
+ AppOpEnums.APP_OP_READ_DEVICE_IDENTIFIERS;
/** @hide Read location metadata from media */
- public static final int OP_ACCESS_MEDIA_LOCATION = AppProtoEnums.APP_OP_ACCESS_MEDIA_LOCATION;
+ public static final int OP_ACCESS_MEDIA_LOCATION = AppOpEnums.APP_OP_ACCESS_MEDIA_LOCATION;
/** @hide Query all apps on device, regardless of declarations in the calling app manifest */
- public static final int OP_QUERY_ALL_PACKAGES = AppProtoEnums.APP_OP_QUERY_ALL_PACKAGES;
+ public static final int OP_QUERY_ALL_PACKAGES = AppOpEnums.APP_OP_QUERY_ALL_PACKAGES;
/** @hide Access all external storage */
- public static final int OP_MANAGE_EXTERNAL_STORAGE =
- AppProtoEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
+ public static final int OP_MANAGE_EXTERNAL_STORAGE = AppOpEnums.APP_OP_MANAGE_EXTERNAL_STORAGE;
/** @hide Communicate cross-profile within the same profile group. */
public static final int OP_INTERACT_ACROSS_PROFILES =
- AppProtoEnums.APP_OP_INTERACT_ACROSS_PROFILES;
+ AppOpEnums.APP_OP_INTERACT_ACROSS_PROFILES;
/**
* Start (without additional user intervention) a Platform VPN connection, as used by {@link
* android.net.VpnManager}
@@ -1225,16 +1221,16 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_ACTIVATE_PLATFORM_VPN = AppProtoEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
+ public static final int OP_ACTIVATE_PLATFORM_VPN = AppOpEnums.APP_OP_ACTIVATE_PLATFORM_VPN;
/** @hide Controls whether or not read logs are available for incremental installations. */
- public static final int OP_LOADER_USAGE_STATS = AppProtoEnums.APP_OP_LOADER_USAGE_STATS;
+ public static final int OP_LOADER_USAGE_STATS = AppOpEnums.APP_OP_LOADER_USAGE_STATS;
// App op deprecated/removed.
- private static final int OP_DEPRECATED_1 = AppProtoEnums.APP_OP_DEPRECATED_1;
+ private static final int OP_DEPRECATED_1 = AppOpEnums.APP_OP_DEPRECATED_1;
/** @hide Auto-revoke app permissions if app is unused for an extended period */
public static final int OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED =
- AppProtoEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
+ AppOpEnums.APP_OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED;
/**
* Whether {@link #OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED} is allowed to be changed by
@@ -1243,55 +1239,55 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_AUTO_REVOKE_MANAGED_BY_INSTALLER =
- AppProtoEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
+ AppOpEnums.APP_OP_AUTO_REVOKE_MANAGED_BY_INSTALLER;
/** @hide */
- public static final int OP_NO_ISOLATED_STORAGE = AppProtoEnums.APP_OP_NO_ISOLATED_STORAGE;
+ public static final int OP_NO_ISOLATED_STORAGE = AppOpEnums.APP_OP_NO_ISOLATED_STORAGE;
/**
* Phone call is using microphone
*
* @hide
*/
- public static final int OP_PHONE_CALL_MICROPHONE = AppProtoEnums.APP_OP_PHONE_CALL_MICROPHONE;
+ public static final int OP_PHONE_CALL_MICROPHONE = AppOpEnums.APP_OP_PHONE_CALL_MICROPHONE;
/**
* Phone call is using camera
*
* @hide
*/
- public static final int OP_PHONE_CALL_CAMERA = AppProtoEnums.APP_OP_PHONE_CALL_CAMERA;
+ public static final int OP_PHONE_CALL_CAMERA = AppOpEnums.APP_OP_PHONE_CALL_CAMERA;
/**
* Audio is being recorded for hotword detection.
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_HOTWORD = AppProtoEnums.APP_OP_RECORD_AUDIO_HOTWORD;
+ public static final int OP_RECORD_AUDIO_HOTWORD = AppOpEnums.APP_OP_RECORD_AUDIO_HOTWORD;
/**
* Manage credentials in the system KeyChain.
*
* @hide
*/
- public static final int OP_MANAGE_CREDENTIALS = AppProtoEnums.APP_OP_MANAGE_CREDENTIALS;
+ public static final int OP_MANAGE_CREDENTIALS = AppOpEnums.APP_OP_MANAGE_CREDENTIALS;
/** @hide */
public static final int OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
- AppProtoEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
+ AppOpEnums.APP_OP_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER;
/**
* App output audio is being recorded
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_OUTPUT = AppProtoEnums.APP_OP_RECORD_AUDIO_OUTPUT;
+ public static final int OP_RECORD_AUDIO_OUTPUT = AppOpEnums.APP_OP_RECORD_AUDIO_OUTPUT;
/**
* App can schedule exact alarm to perform timing based background work
*
* @hide
*/
- public static final int OP_SCHEDULE_EXACT_ALARM = AppProtoEnums.APP_OP_SCHEDULE_EXACT_ALARM;
+ public static final int OP_SCHEDULE_EXACT_ALARM = AppOpEnums.APP_OP_SCHEDULE_EXACT_ALARM;
/**
* Fine location being accessed by a location source, which is
@@ -1301,7 +1297,7 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_FINE_LOCATION_SOURCE = AppProtoEnums.APP_OP_FINE_LOCATION_SOURCE;
+ public static final int OP_FINE_LOCATION_SOURCE = AppOpEnums.APP_OP_FINE_LOCATION_SOURCE;
/**
* Coarse location being accessed by a location source, which is
@@ -1311,7 +1307,7 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_COARSE_LOCATION_SOURCE = AppProtoEnums.APP_OP_COARSE_LOCATION_SOURCE;
+ public static final int OP_COARSE_LOCATION_SOURCE = AppOpEnums.APP_OP_COARSE_LOCATION_SOURCE;
/**
* Allow apps to create the requests to manage the media files without user confirmation.
@@ -1323,13 +1319,13 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_MANAGE_MEDIA = AppProtoEnums.APP_OP_MANAGE_MEDIA;
+ public static final int OP_MANAGE_MEDIA = AppOpEnums.APP_OP_MANAGE_MEDIA;
/** @hide */
- public static final int OP_UWB_RANGING = AppProtoEnums.APP_OP_UWB_RANGING;
+ public static final int OP_UWB_RANGING = AppOpEnums.APP_OP_UWB_RANGING;
/** @hide */
- public static final int OP_NEARBY_WIFI_DEVICES = AppProtoEnums.APP_OP_NEARBY_WIFI_DEVICES;
+ public static final int OP_NEARBY_WIFI_DEVICES = AppOpEnums.APP_OP_NEARBY_WIFI_DEVICES;
/**
* Activity recognition being accessed by an activity recognition source, which
@@ -1339,7 +1335,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_ACTIVITY_RECOGNITION_SOURCE =
- AppProtoEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
+ AppOpEnums.APP_OP_ACTIVITY_RECOGNITION_SOURCE;
/**
* Incoming phone audio is being recorded
@@ -1347,21 +1343,21 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_RECORD_INCOMING_PHONE_AUDIO =
- AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
+ AppOpEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO;
/**
* VPN app establishes a connection through the VpnService API.
*
* @hide
*/
- public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE;
+ public static final int OP_ESTABLISH_VPN_SERVICE = AppOpEnums.APP_OP_ESTABLISH_VPN_SERVICE;
/**
* VPN app establishes a connection through the VpnManager API.
*
* @hide
*/
- public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER;
+ public static final int OP_ESTABLISH_VPN_MANAGER = AppOpEnums.APP_OP_ESTABLISH_VPN_MANAGER;
/**
* Access restricted settings.
@@ -1369,7 +1365,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_ACCESS_RESTRICTED_SETTINGS =
- AppProtoEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
+ AppOpEnums.APP_OP_ACCESS_RESTRICTED_SETTINGS;
/**
* Receive microphone audio from an ambient sound detection event
@@ -1377,7 +1373,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_RECEIVE_AMBIENT_TRIGGER_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
/**
* Receive audio from near-field mic (ie. TV remote)
@@ -1387,15 +1383,14 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
/**
* App can schedule user-initiated jobs.
*
* @hide
*/
- public static final int OP_RUN_USER_INITIATED_JOBS =
- AppProtoEnums.APP_OP_RUN_USER_INITIATED_JOBS;
+ public static final int OP_RUN_USER_INITIATED_JOBS = AppOpEnums.APP_OP_RUN_USER_INITIATED_JOBS;
/**
* Notify apps that they have been granted URI permission photos
@@ -1403,7 +1398,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
- AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+ AppOpEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
/**
* Prevent an app from being suspended.
@@ -1413,7 +1408,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_SUSPENSION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_SUSPENSION;
/**
* Prevent an app from dismissible notifications. Starting from Android U, notifications with
@@ -1425,14 +1420,14 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_DISMISSIBLE_NOTIFICATIONS;
/**
* An app op for reading/writing health connect data.
*
* @hide
*/
- public static final int OP_READ_WRITE_HEALTH_DATA = AppProtoEnums.APP_OP_READ_WRITE_HEALTH_DATA;
+ public static final int OP_READ_WRITE_HEALTH_DATA = AppOpEnums.APP_OP_READ_WRITE_HEALTH_DATA;
/**
* Use foreground service with the type
@@ -1441,7 +1436,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_FOREGROUND_SERVICE_SPECIAL_USE =
- AppProtoEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
+ AppOpEnums.APP_OP_FOREGROUND_SERVICE_SPECIAL_USE;
/**
* Exempt an app from all power-related restrictions, including app standby and doze.
@@ -1453,7 +1448,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_POWER_RESTRICTIONS;
/**
* Prevent an app from being placed into hibernation.
@@ -1463,7 +1458,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_HIBERNATION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_HIBERNATION;
/**
* Allows an application to start an activity while running in the background.
@@ -1473,7 +1468,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION =
- AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
+ AppOpEnums.APP_OP_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION;
/**
* Allows an application to capture bugreport directly without consent dialog when using the
@@ -1482,33 +1477,31 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD =
- AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
+ AppOpEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD;
// App op deprecated/removed.
- private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
+ private static final int OP_DEPRECATED_2 = AppOpEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE;
/**
* Send an intent to launch instead of posting the notification to the status bar.
*
* @hide
*/
- public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT;
+ public static final int OP_USE_FULL_SCREEN_INTENT = AppOpEnums.APP_OP_USE_FULL_SCREEN_INTENT;
/**
* Hides camera indicator for sandboxed detection apps that directly access the service.
*
* @hide
*/
- public static final int OP_CAMERA_SANDBOXED =
- AppProtoEnums.APP_OP_CAMERA_SANDBOXED;
+ public static final int OP_CAMERA_SANDBOXED = AppOpEnums.APP_OP_CAMERA_SANDBOXED;
/**
* Hides microphone indicator for sandboxed detection apps that directly access the service.
*
* @hide
*/
- public static final int OP_RECORD_AUDIO_SANDBOXED =
- AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
+ public static final int OP_RECORD_AUDIO_SANDBOXED = AppOpEnums.APP_OP_RECORD_AUDIO_SANDBOXED;
/**
* Allows the assistant app to be voice-triggered by detected hotwords from a trusted detection
@@ -1517,14 +1510,14 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_RECEIVE_SANDBOX_TRIGGER_AUDIO =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
+ AppOpEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO;
/**
* This op has been deprecated.
*
*/
private static final int OP_DEPRECATED_3 =
- AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
+ AppOpEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA;
/**
* Creation of an overlay using accessibility services
@@ -1532,20 +1525,20 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_CREATE_ACCESSIBILITY_OVERLAY =
- AppProtoEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
+ AppOpEnums.APP_OP_CREATE_ACCESSIBILITY_OVERLAY;
/**
* Indicate that the user has enabled or disabled mobile data
* @hide
*/
public static final int OP_ENABLE_MOBILE_DATA_BY_USER =
- AppProtoEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
+ AppOpEnums.APP_OP_ENABLE_MOBILE_DATA_BY_USER;
/**
* See {@link #OPSTR_MEDIA_ROUTING_CONTROL}.
* @hide
*/
- public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL;
+ public static final int OP_MEDIA_ROUTING_CONTROL = AppOpEnums.APP_OP_MEDIA_ROUTING_CONTROL;
/**
* Op code for use by tests to avoid interfering history logs that the wider system might
@@ -1553,7 +1546,7 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING;
+ public static final int OP_RESERVED_FOR_TESTING = AppOpEnums.APP_OP_RESERVED_FOR_TESTING;
/**
* Rapid clearing of notifications by a notification listener
@@ -1562,28 +1555,28 @@ public class AppOpsManager {
*/
// See b/289080543 for more details
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
- AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+ AppOpEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
/**
* See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
* @hide
*/
public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
- AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+ AppOpEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
/**
* This app has been removed..
*
* @hide
*/
- private static final int OP_DEPRECATED_4 = AppProtoEnums.APP_OP_RUN_BACKUP_JOBS;
+ private static final int OP_DEPRECATED_4 = AppOpEnums.APP_OP_RUN_BACKUP_JOBS;
/**
* Whether the app has enabled to receive the icon overlay for fetching archived apps.
*
* @hide
*/
- public static final int OP_ARCHIVE_ICON_OVERLAY = AppProtoEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
+ public static final int OP_ARCHIVE_ICON_OVERLAY = AppOpEnums.APP_OP_ARCHIVE_ICON_OVERLAY;
/**
* Whether the app has enabled compatibility support for unarchival.
@@ -1591,7 +1584,7 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_UNARCHIVAL_CONFIRMATION =
- AppProtoEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
+ AppOpEnums.APP_OP_UNARCHIVAL_CONFIRMATION;
/**
* Allows an app to access location without the traditional location permissions and while the
@@ -1603,7 +1596,7 @@ public class AppOpsManager {
*
* @hide
*/
- public static final int OP_EMERGENCY_LOCATION = AppProtoEnums.APP_OP_EMERGENCY_LOCATION;
+ public static final int OP_EMERGENCY_LOCATION = AppOpEnums.APP_OP_EMERGENCY_LOCATION;
/**
* Allows apps with a NotificationListenerService to receive notifications with sensitive
@@ -1613,13 +1606,13 @@ public class AppOpsManager {
* @hide
*/
public static final int OP_RECEIVE_SENSITIVE_NOTIFICATIONS =
- AppProtoEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
+ AppOpEnums.APP_OP_RECEIVE_SENSITIVE_NOTIFICATIONS;
/** @hide Access to read heart rate sensor. */
- public static final int OP_READ_HEART_RATE = AppProtoEnums.APP_OP_READ_HEART_RATE;
+ public static final int OP_READ_HEART_RATE = AppOpEnums.APP_OP_READ_HEART_RATE;
/** @hide Access to read skin temperature. */
- public static final int OP_READ_SKIN_TEMPERATURE = AppProtoEnums.APP_OP_READ_SKIN_TEMPERATURE;
+ public static final int OP_READ_SKIN_TEMPERATURE = AppOpEnums.APP_OP_READ_SKIN_TEMPERATURE;
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -7807,6 +7800,116 @@ public class AppOpsManager {
}
}
+ private static final String APP_OP_MODE_CACHING_API = "getAppOpMode";
+ private static final String APP_OP_MODE_CACHING_NAME = "appOpModeCache";
+ private static final int APP_OP_MODE_CACHING_SIZE = 2048;
+
+ private static final IpcDataCache.QueryHandler<AppOpModeQuery, Integer> sGetAppOpModeQuery =
+ new IpcDataCache.QueryHandler<>() {
+ @Override
+ public Integer apply(AppOpModeQuery query) {
+ IAppOpsService service = getService();
+ try {
+ return service.checkOperationRawForDevice(query.op, query.uid,
+ query.packageName, query.attributionTag, query.virtualDeviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean shouldBypassCache(@NonNull AppOpModeQuery query) {
+ // If the flag to enable the new caching behavior is off, bypass the cache.
+ return !Flags.appopModeCachingEnabled();
+ }
+ };
+
+ // A LRU cache on binder clients that caches AppOp mode by uid, packageName, virtualDeviceId
+ // and attributionTag.
+ private static final IpcDataCache<AppOpModeQuery, Integer> sAppOpModeCache =
+ new IpcDataCache<>(APP_OP_MODE_CACHING_SIZE, IpcDataCache.MODULE_SYSTEM,
+ APP_OP_MODE_CACHING_API, APP_OP_MODE_CACHING_NAME, sGetAppOpModeQuery);
+
+ // Ops that we don't want to cache due to:
+ // 1) Discrepancy of attributionTag support in checkOp and noteOp that determines if a package
+ // can bypass user restriction of an op: b/240617242. COARSE_LOCATION and FINE_LOCATION are
+ // the only two ops that are impacted.
+ private static final SparseBooleanArray OPS_WITHOUT_CACHING = new SparseBooleanArray();
+ static {
+ OPS_WITHOUT_CACHING.put(OP_COARSE_LOCATION, true);
+ OPS_WITHOUT_CACHING.put(OP_FINE_LOCATION, true);
+ }
+
+ private static boolean isAppOpModeCachingEnabled(int opCode) {
+ if (!Flags.appopModeCachingEnabled()) {
+ return false;
+ }
+ return !OPS_WITHOUT_CACHING.get(opCode, false);
+ }
+
+ /**
+ * @hide
+ */
+ public static void invalidateAppOpModeCache() {
+ if (Flags.appopModeCachingEnabled()) {
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_SYSTEM, APP_OP_MODE_CACHING_API);
+ }
+ }
+
+ /**
+ * Bypass AppOpModeCache in the local process
+ *
+ * @hide
+ */
+ public static void disableAppOpModeCache() {
+ if (Flags.appopModeCachingEnabled()) {
+ sAppOpModeCache.disableLocal();
+ }
+ }
+
+ private static final class AppOpModeQuery {
+ final int op;
+ final int uid;
+ final String packageName;
+ final int virtualDeviceId;
+ final String attributionTag;
+ final String methodName;
+
+ AppOpModeQuery(int op, int uid, @Nullable String packageName, int virtualDeviceId,
+ @Nullable String attributionTag, @Nullable String methodName) {
+ this.op = op;
+ this.uid = uid;
+ this.packageName = packageName;
+ this.virtualDeviceId = virtualDeviceId;
+ this.attributionTag = attributionTag;
+ this.methodName = methodName;
+ }
+
+ @Override
+ public String toString() {
+ return TextUtils.formatSimple("AppOpModeQuery(op=%d, uid=%d, packageName=%s, "
+ + "virtualDeviceId=%d, attributionTag=%s, methodName=%s", op, uid,
+ packageName, virtualDeviceId, attributionTag, methodName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(op, uid, packageName, virtualDeviceId, attributionTag);
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ if (this.getClass() != o.getClass()) return false;
+
+ AppOpModeQuery other = (AppOpModeQuery) o;
+ return op == other.op && uid == other.uid && Objects.equals(packageName,
+ other.packageName) && virtualDeviceId == other.virtualDeviceId
+ && Objects.equals(attributionTag, other.attributionTag);
+ }
+ }
+
AppOpsManager(Context context, IAppOpsService service) {
mContext = context;
mService = service;
@@ -8861,12 +8964,16 @@ public class AppOpsManager {
private int unsafeCheckOpRawNoThrow(int op, int uid, @NonNull String packageName,
int virtualDeviceId) {
try {
- if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
- return mService.checkOperationRaw(op, uid, packageName, null);
+ int mode;
+ if (isAppOpModeCachingEnabled(op)) {
+ mode = sAppOpModeCache.query(
+ new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null,
+ "unsafeCheckOpRawNoThrow"));
} else {
- return mService.checkOperationRawForDevice(
+ mode = mService.checkOperationRawForDevice(
op, uid, packageName, null, virtualDeviceId);
}
+ return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -9051,7 +9158,7 @@ public class AppOpsManager {
SyncNotedAppOp syncOp;
if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
syncOp = mService.noteOperation(op, uid, packageName, attributionTag,
- collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
+ collectionMode == COLLECT_ASYNC, message, shouldCollectMessage);
} else {
syncOp = mService.noteOperationForDevice(op, uid, packageName, attributionTag,
virtualDeviceId, collectionMode == COLLECT_ASYNC, message,
@@ -9294,8 +9401,21 @@ public class AppOpsManager {
@UnsupportedAppUsage
public int checkOp(int op, int uid, String packageName) {
try {
- int mode = mService.checkOperationForDevice(op, uid, packageName,
- Context.DEVICE_ID_DEFAULT);
+ int mode;
+ if (isAppOpModeCachingEnabled(op)) {
+ mode = sAppOpModeCache.query(
+ new AppOpModeQuery(op, uid, packageName, Context.DEVICE_ID_DEFAULT, null,
+ "checkOp"));
+ if (mode == MODE_FOREGROUND) {
+ // We only cache raw mode. If the mode is FOREGROUND, we need another binder
+ // call to fetch translated value based on the process state.
+ mode = mService.checkOperationForDevice(op, uid, packageName,
+ Context.DEVICE_ID_DEFAULT);
+ }
+ } else {
+ mode = mService.checkOperationForDevice(op, uid, packageName,
+ Context.DEVICE_ID_DEFAULT);
+ }
if (mode == MODE_ERRORED) {
throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
}
@@ -9334,13 +9454,19 @@ public class AppOpsManager {
private int checkOpNoThrow(int op, int uid, String packageName, int virtualDeviceId) {
try {
int mode;
- if (virtualDeviceId == Context.DEVICE_ID_DEFAULT) {
- mode = mService.checkOperation(op, uid, packageName);
+ if (isAppOpModeCachingEnabled(op)) {
+ mode = sAppOpModeCache.query(
+ new AppOpModeQuery(op, uid, packageName, virtualDeviceId, null,
+ "checkOpNoThrow"));
+ if (mode == MODE_FOREGROUND) {
+ // We only cache raw mode. If the mode is FOREGROUND, we need another binder
+ // call to fetch translated value based on the process state.
+ mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId);
+ }
} else {
mode = mService.checkOperationForDevice(op, uid, packageName, virtualDeviceId);
}
-
- return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
+ return mode;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index 63e039143917..b7285c38290c 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -602,6 +602,15 @@ public class StatusBarManager {
@LoggingOnly
private static final long MEDIA_CONTROL_BLANK_TITLE = 274775190L;
+ /**
+ * Media controls based on {@link android.app.Notification.MediaStyle} notifications will have
+ * actions from the associated {@link androidx.media3.MediaController}, if available.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT)
+ // TODO(b/360196209): Set target SDK to Baklava once available
+ private static final long MEDIA_CONTROL_MEDIA3_ACTIONS = 360196209L;
+
@UnsupportedAppUsage
private Context mContext;
private IStatusBarService mService;
@@ -1270,6 +1279,21 @@ public class StatusBarManager {
}
/**
+ * Checks whether the media controls for a given package should use a Media3 controller
+ *
+ * @param packageName App posting media controls
+ * @param user Current user handle
+ * @return true if Media3 should be used
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+ android.Manifest.permission.LOG_COMPAT_CHANGE})
+ public static boolean useMedia3ControllerForApp(String packageName, UserHandle user) {
+ return CompatChanges.isChangeEnabled(MEDIA_CONTROL_MEDIA3_ACTIONS, packageName, user);
+ }
+
+ /**
* Checks whether the supplied activity can {@link Activity#startActivityForResult(Intent, int)}
* a system activity that captures content on the screen to take a screenshot.
*
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 9c6f509c93c1..aac963ade726 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -296,22 +296,22 @@ public class TaskInfo {
public boolean isVisibleRequested;
/**
- * Whether this task is sleeping due to sleeping display.
+ * Whether the top activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
* @hide
*/
- public boolean isSleeping;
+ public boolean isTopActivityNoDisplay;
/**
- * Whether the top activity fillsParent() is false
+ * Whether this task is sleeping due to sleeping display.
* @hide
*/
- public boolean isTopActivityTransparent;
+ public boolean isSleeping;
/**
- * Whether the top activity has specified style floating.
+ * Whether the top activity fillsParent() is false
* @hide
*/
- public boolean isTopActivityStyleFloating;
+ public boolean isTopActivityTransparent;
/**
* The last non-fullscreen bounds the task was launched in or resized to.
@@ -364,8 +364,9 @@ public class TaskInfo {
// Do nothing
}
- private TaskInfo(Parcel source) {
- readFromParcel(source);
+ /** @hide */
+ public TaskInfo(Parcel source) {
+ readTaskFromParcel(source);
}
/**
@@ -482,12 +483,12 @@ public class TaskInfo {
&& isFocused == that.isFocused
&& isVisible == that.isVisible
&& isVisibleRequested == that.isVisibleRequested
+ && isTopActivityNoDisplay == that.isTopActivityNoDisplay
&& isSleeping == that.isSleeping
&& Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
&& parentTaskId == that.parentTaskId
&& Objects.equals(topActivity, that.topActivity)
&& isTopActivityTransparent == that.isTopActivityTransparent
- && isTopActivityStyleFloating == that.isTopActivityStyleFloating
&& Objects.equals(lastNonFullscreenBounds, that.lastNonFullscreenBounds)
&& Objects.equals(capturedLink, that.capturedLink)
&& capturedLinkTimestamp == that.capturedLinkTimestamp
@@ -524,7 +525,7 @@ public class TaskInfo {
/**
* Reads the TaskInfo from a parcel.
*/
- void readFromParcel(Parcel source) {
+ void readTaskFromParcel(Parcel source) {
userId = source.readInt();
taskId = source.readInt();
effectiveUid = source.readInt();
@@ -561,11 +562,11 @@ public class TaskInfo {
isFocused = source.readBoolean();
isVisible = source.readBoolean();
isVisibleRequested = source.readBoolean();
+ isTopActivityNoDisplay = source.readBoolean();
isSleeping = source.readBoolean();
mTopActivityLocusId = source.readTypedObject(LocusId.CREATOR);
displayAreaFeatureId = source.readInt();
isTopActivityTransparent = source.readBoolean();
- isTopActivityStyleFloating = source.readBoolean();
lastNonFullscreenBounds = source.readTypedObject(Rect.CREATOR);
capturedLink = source.readTypedObject(Uri.CREATOR);
capturedLinkTimestamp = source.readLong();
@@ -577,8 +578,9 @@ public class TaskInfo {
/**
* Writes the TaskInfo to a parcel.
+ * @hide
*/
- void writeToParcel(Parcel dest, int flags) {
+ public void writeTaskToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeInt(taskId);
dest.writeInt(effectiveUid);
@@ -616,11 +618,11 @@ public class TaskInfo {
dest.writeBoolean(isFocused);
dest.writeBoolean(isVisible);
dest.writeBoolean(isVisibleRequested);
+ dest.writeBoolean(isTopActivityNoDisplay);
dest.writeBoolean(isSleeping);
dest.writeTypedObject(mTopActivityLocusId, flags);
dest.writeInt(displayAreaFeatureId);
dest.writeBoolean(isTopActivityTransparent);
- dest.writeBoolean(isTopActivityStyleFloating);
dest.writeTypedObject(lastNonFullscreenBounds, flags);
dest.writeTypedObject(capturedLink, flags);
dest.writeLong(capturedLinkTimestamp);
@@ -661,11 +663,11 @@ public class TaskInfo {
+ " isFocused=" + isFocused
+ " isVisible=" + isVisible
+ " isVisibleRequested=" + isVisibleRequested
+ + " isTopActivityNoDisplay=" + isTopActivityNoDisplay
+ " isSleeping=" + isSleeping
+ " locusId=" + mTopActivityLocusId
+ " displayAreaFeatureId=" + displayAreaFeatureId
+ " isTopActivityTransparent=" + isTopActivityTransparent
- + " isTopActivityStyleFloating=" + isTopActivityStyleFloating
+ " lastNonFullscreenBounds=" + lastNonFullscreenBounds
+ " capturedLink=" + capturedLink
+ " capturedLinkTimestamp=" + capturedLinkTimestamp
diff --git a/core/java/android/app/admin/DevicePolicyIdentifiers.java b/core/java/android/app/admin/DevicePolicyIdentifiers.java
index c0e435c04d3c..35149b5a3135 100644
--- a/core/java/android/app/admin/DevicePolicyIdentifiers.java
+++ b/core/java/android/app/admin/DevicePolicyIdentifiers.java
@@ -191,6 +191,12 @@ public final class DevicePolicyIdentifiers {
public static final String PASSWORD_COMPLEXITY_POLICY = "passwordComplexity";
/**
+ * String identifier for {@link DevicePolicyManager#setMtePolicy(int)}.
+ */
+ @FlaggedApi(android.app.admin.flags.Flags.FLAG_SET_MTE_POLICY_COEXISTENCE)
+ public static final String MEMORY_TAGGING_POLICY = "memoryTagging";
+
+ /**
* @hide
*/
public static final String USER_RESTRICTION_PREFIX = "userRestriction_";
diff --git a/core/java/android/app/jank/JankTracker.java b/core/java/android/app/jank/JankTracker.java
new file mode 100644
index 000000000000..df422e0069c5
--- /dev/null
+++ b/core/java/android/app/jank/JankTracker.java
@@ -0,0 +1,219 @@
+/*
+ * 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.app.jank;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.view.AttachedSurfaceControl;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+
+/**
+ * This class is responsible for registering callbacks that will receive JankData batches.
+ * It handles managing the background thread that JankData will be processed on. As well as acting
+ * as an intermediary between widgets and the state tracker, routing state changes to the tracker.
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+public class JankTracker {
+
+ // Tracks states reported by widgets.
+ private StateTracker mStateTracker;
+ // Processes JankData batches and associates frames to widget states.
+ private JankDataProcessor mJankDataProcessor;
+
+ // Background thread responsible for processing JankData batches.
+ private HandlerThread mHandlerThread = new HandlerThread("AppJankTracker");
+ private Handler mHandler = null;
+
+ // Needed so we know when the view is attached to a window.
+ private ViewTreeObserver mViewTreeObserver;
+
+ // Handle to a registered OnJankData listener.
+ private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
+
+ // The interface to the windowing system that enables us to register for JankData.
+ private AttachedSurfaceControl mSurfaceControl;
+ // Name of the activity that is currently tracking Jank metrics.
+ private String mActivityName;
+ // The apps uid.
+ private int mAppUid;
+ // View that gives us access to ViewTreeObserver.
+ private View mDecorView;
+
+ /**
+ * Set by the activity to enable or disable jank tracking. Activities may disable tracking if
+ * they are paused or not enable tracking if they are not visible or if the app category is not
+ * set.
+ */
+ private boolean mTrackingEnabled = false;
+ /**
+ * Set to true once listeners are registered and JankData will start to be received. Both
+ * mTrackingEnabled and mListenersRegistered need to be true for JankData to be processed.
+ */
+ private boolean mListenersRegistered = false;
+
+
+ public JankTracker(Choreographer choreographer, View decorView) {
+ mStateTracker = new StateTracker(choreographer);
+ mJankDataProcessor = new JankDataProcessor(mStateTracker);
+ mDecorView = decorView;
+ mHandlerThread.start();
+ registerWindowListeners();
+ }
+
+ public void setActivityName(@NonNull String activityName) {
+ mActivityName = activityName;
+ }
+
+ public void setAppUid(int uid) {
+ mAppUid = uid;
+ }
+
+ /**
+ * Will add the widget category, id and state as a UI state to associate frames to it.
+ * @param widgetCategory preselected general widget category
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState the current active widget state.
+ */
+ public void addUiState(String widgetCategory, String widgetId, String widgetState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.putState(widgetCategory, widgetId, widgetState);
+ }
+
+ /**
+ * Will remove the widget category, id and state as a ui state and no longer attribute frames
+ * to it.
+ * @param widgetCategory preselected general widget category
+ * @param widgetId developer defined widget id if available.
+ * @param widgetState no longer active widget state.
+ */
+ public void removeUiState(String widgetCategory, String widgetId, String widgetState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.removeState(widgetCategory, widgetId, widgetState);
+ }
+
+ /**
+ * Call to update a jank state to a different state.
+ * @param widgetCategory preselected general widget category.
+ * @param widgetId developer defined widget id if available.
+ * @param currentState current state of the widget.
+ * @param nextState the state the widget will be in.
+ */
+ public void updateUiState(String widgetCategory, String widgetId, String currentState,
+ String nextState) {
+ if (!shouldTrack()) return;
+
+ mStateTracker.updateState(widgetCategory, widgetId, currentState, nextState);
+ }
+
+ /**
+ * Will enable jank tracking, and add the activity as a state to associate frames to.
+ */
+ public void enableAppJankTracking() {
+ // Add the activity as a state, this will ensure we track frames to the activity without the
+ // need of a decorated widget to be used.
+ // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+ mStateTracker.putState("NONE", mActivityName, "NONE");
+ mTrackingEnabled = true;
+ }
+
+ /**
+ * Will disable jank tracking, and remove the activity as a state to associate frames to.
+ */
+ public void disableAppJankTracking() {
+ mTrackingEnabled = false;
+ // TODO b/376116199 replace "NONE" with UNSPECIFIED once the API changes are merged.
+ mStateTracker.removeState("NONE", mActivityName, "NONE");
+ }
+
+ /**
+ * Retrieve all pending widget states, this is intended for testing purposes only.
+ * @param stateDataList the ArrayList that will be populated with the pending states.
+ */
+ @VisibleForTesting
+ public void getAllUiStates(@NonNull ArrayList<StateTracker.StateData> stateDataList) {
+ mStateTracker.retrieveAllStates(stateDataList);
+ }
+
+ /**
+ * Only intended to be used by tests, the runnable that registers the listeners may not run
+ * in time for tests to pass. This forces them to run immediately.
+ */
+ @VisibleForTesting
+ public void forceListenerRegistration() {
+ mSurfaceControl = mDecorView.getRootSurfaceControl();
+ registerForJankData();
+ // TODO b/376116199 Check if registration is good.
+ mListenersRegistered = true;
+ }
+
+ private void registerForJankData() {
+ if (mSurfaceControl == null) return;
+ /*
+ TODO b/376115668 Register for JankData batches from new JankTracking API
+ */
+ }
+
+ private boolean shouldTrack() {
+ return mTrackingEnabled && mListenersRegistered;
+ }
+
+ /**
+ * Need to know when the decor view gets attached to the window in order to get
+ * AttachedSurfaceControl. In order to register a callback for OnJankDataListener
+ * AttachedSurfaceControl needs to be created which only happens after onWindowAttached is
+ * called. This is why there is a delay in posting the runnable.
+ */
+ private void registerWindowListeners() {
+ if (mDecorView == null) return;
+ mViewTreeObserver = mDecorView.getViewTreeObserver();
+ mViewTreeObserver.addOnWindowAttachListener(new ViewTreeObserver.OnWindowAttachListener() {
+ @Override
+ public void onWindowAttached() {
+ getHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ forceListenerRegistration();
+ }
+ }, 1000);
+ }
+
+ @Override
+ public void onWindowDetached() {
+ // TODO b/376116199 do we un-register the callback or just not process the data.
+ }
+ });
+ }
+
+ private Handler getHandler() {
+ if (mHandler == null) {
+ mHandler = new Handler(mHandlerThread.getLooper());
+ }
+ return mHandler;
+ }
+}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 940a4d208d99..ce515761551c 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -55,7 +55,7 @@ flag {
name: "remote_views_proto"
namespace: "app_widgets"
description: "Enable support for persisting RemoteViews previews to Protobuf"
- bug: "306546610"
+ bug: "364420494"
}
flag {
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 65f9cbefb052..2be27dabcf90 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -160,7 +160,7 @@ public final class VirtualDeviceParams implements Parcelable {
*/
@IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY, POLICY_TYPE_CLIPBOARD, POLICY_TYPE_CAMERA,
- POLICY_TYPE_BLOCKED_ACTIVITY})
+ POLICY_TYPE_BLOCKED_ACTIVITY, POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS})
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface PolicyType {}
@@ -301,6 +301,21 @@ public final class VirtualDeviceParams implements Parcelable {
@FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
public static final int POLICY_TYPE_BLOCKED_ACTIVITY = 6;
+ /**
+ * Tells the virtual device framework how to handle camera access of the default device by apps
+ * running on the virtual device.
+ *
+ * <ul>
+ * <li>{@link #DEVICE_POLICY_DEFAULT}: Default device camera access will be allowed.
+ * <li>{@link #DEVICE_POLICY_CUSTOM}: Default device camera access will be blocked.
+ * </ul>
+ *
+ * @see Context#DEVICE_ID_DEFAULT
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags
+ .FLAG_DEFAULT_DEVICE_CAMERA_ACCESS_POLICY)
+ public static final int POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS = 7;
+
private final int mLockState;
@NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
@NavigationPolicy
@@ -318,6 +333,8 @@ public final class VirtualDeviceParams implements Parcelable {
@Nullable private final IVirtualSensorCallback mVirtualSensorCallback;
private final int mAudioPlaybackSessionId;
private final int mAudioRecordingSessionId;
+ private final long mDimDuration;
+ private final long mScreenOffTimeout;
private VirtualDeviceParams(
@LockState int lockState,
@@ -333,7 +350,9 @@ public final class VirtualDeviceParams implements Parcelable {
@NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@Nullable IVirtualSensorCallback virtualSensorCallback,
int audioPlaybackSessionId,
- int audioRecordingSessionId) {
+ int audioRecordingSessionId,
+ long dimDuration,
+ long screenOffTimeout) {
mLockState = lockState;
mUsersWithMatchingAccounts =
new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -351,6 +370,8 @@ public final class VirtualDeviceParams implements Parcelable {
mVirtualSensorCallback = virtualSensorCallback;
mAudioPlaybackSessionId = audioPlaybackSessionId;
mAudioRecordingSessionId = audioRecordingSessionId;
+ mDimDuration = dimDuration;
+ mScreenOffTimeout = screenOffTimeout;
}
@SuppressWarnings("unchecked")
@@ -371,6 +392,8 @@ public final class VirtualDeviceParams implements Parcelable {
mAudioRecordingSessionId = parcel.readInt();
mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR);
mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR);
+ mDimDuration = parcel.readLong();
+ mScreenOffTimeout = parcel.readLong();
}
/**
@@ -382,6 +405,26 @@ public final class VirtualDeviceParams implements Parcelable {
}
/**
+ * Returns the dim duration for the displays of this device.
+ *
+ * @see Builder#setDimDuration(Duration)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public @NonNull Duration getDimDuration() {
+ return Duration.ofMillis(mDimDuration);
+ }
+
+ /**
+ * Returns the screen off timeout of the displays of this device.
+ *
+ * @see Builder#setDimDuration(Duration)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ public @NonNull Duration getScreenOffTimeout() {
+ return Duration.ofMillis(mScreenOffTimeout);
+ }
+
+ /**
* Returns the custom component used as home on all displays owned by this virtual device that
* support home activities.
*
@@ -604,6 +647,8 @@ public final class VirtualDeviceParams implements Parcelable {
dest.writeInt(mAudioRecordingSessionId);
dest.writeTypedObject(mHomeComponent, flags);
dest.writeTypedObject(mInputMethodComponent, flags);
+ dest.writeLong(mDimDuration);
+ dest.writeLong(mScreenOffTimeout);
}
@Override
@@ -638,7 +683,9 @@ public final class VirtualDeviceParams implements Parcelable {
&& Objects.equals(mHomeComponent, that.mHomeComponent)
&& Objects.equals(mInputMethodComponent, that.mInputMethodComponent)
&& mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
- && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
+ && mAudioRecordingSessionId == that.mAudioRecordingSessionId
+ && mDimDuration == that.mDimDuration
+ && mScreenOffTimeout == that.mScreenOffTimeout;
}
@Override
@@ -647,7 +694,7 @@ public final class VirtualDeviceParams implements Parcelable {
mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId,
- mAudioRecordingSessionId);
+ mAudioRecordingSessionId, mDimDuration, mScreenOffTimeout);
for (int i = 0; i < mDevicePolicies.size(); i++) {
hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -671,6 +718,8 @@ public final class VirtualDeviceParams implements Parcelable {
+ " mInputMethodComponent=" + mInputMethodComponent
+ " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
+ " mAudioRecordingSessionId=" + mAudioRecordingSessionId
+ + " mDimDuration=" + mDimDuration
+ + " mScreenOffTimeout=" + mScreenOffTimeout
+ ")";
}
@@ -692,11 +741,13 @@ public final class VirtualDeviceParams implements Parcelable {
pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent);
pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId);
+ pw.println(prefix + "mDimDuration=" + mDimDuration);
+ pw.println(prefix + "mScreenOffTimeout=" + mScreenOffTimeout);
}
@NonNull
public static final Parcelable.Creator<VirtualDeviceParams> CREATOR =
- new Parcelable.Creator<VirtualDeviceParams>() {
+ new Parcelable.Creator<>() {
public VirtualDeviceParams createFromParcel(Parcel in) {
return new VirtualDeviceParams(in);
}
@@ -711,6 +762,8 @@ public final class VirtualDeviceParams implements Parcelable {
*/
public static final class Builder {
+ private static final Duration INFINITE_TIMEOUT = Duration.ofDays(365 * 1000);
+
private @LockState int mLockState = LOCK_STATE_DEFAULT;
@NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
@NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
@@ -733,6 +786,8 @@ public final class VirtualDeviceParams implements Parcelable {
@Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback;
@Nullable private ComponentName mHomeComponent;
@Nullable private ComponentName mInputMethodComponent;
+ private Duration mDimDuration = Duration.ZERO;
+ private Duration mScreenOffTimeout = Duration.ZERO;
private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub {
@NonNull
@@ -810,6 +865,57 @@ public final class VirtualDeviceParams implements Parcelable {
}
/**
+ * Sets the dim duration for all trusted non-mirror displays of the device.
+ *
+ * <p>The system will reduce the display brightness for the specified duration if there
+ * has been no interaction just before the displays turn off.</p>
+ *
+ * <p>If set, the screen off timeout must also be set to a value larger than the dim
+ * duration. If left unset or set to zero, then the display brightness will not be reduced.
+ * </p>
+ *
+ * @throws IllegalArgumentException if the dim duration is negative or if the dim duration
+ * is longer than the screen off timeout.
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ * @see #setScreenOffTimeout
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setDimDuration(@NonNull Duration dimDuration) {
+ if (Objects.requireNonNull(dimDuration).compareTo(Duration.ZERO) < 0) {
+ throw new IllegalArgumentException("The dim duration cannot be negative");
+ }
+ mDimDuration = dimDuration;
+ return this;
+ }
+
+ /**
+ * Sets the timeout, after which all trusted non-mirror displays of the device will turn
+ * off, if there has been no interaction with the device.
+ *
+ * <p>If dim duration is set, the screen off timeout must be set to a value larger than the
+ * dim duration. If left unset or set to zero, then the displays will never be turned off
+ * due to inactivity.</p>
+ *
+ * @throws IllegalArgumentException if the screen off timeout is negative or if the dim
+ * duration is longer than the screen off timeout.
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED
+ * @see android.hardware.display.DisplayManager#VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR
+ * @see #setDimDuration
+ * @see VirtualDeviceManager.VirtualDevice#goToSleep()
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @NonNull
+ public Builder setScreenOffTimeout(@NonNull Duration screenOffTimeout) {
+ if (Objects.requireNonNull(screenOffTimeout).compareTo(Duration.ZERO) < 0) {
+ throw new IllegalArgumentException("The screen off timeout cannot be negative");
+ }
+ mScreenOffTimeout = screenOffTimeout;
+ return this;
+ }
+
+ /**
* Specifies a component to be used as home on all displays owned by this virtual device
* that support home activities.
* *
@@ -1205,6 +1311,14 @@ public final class VirtualDeviceParams implements Parcelable {
}
}
+ if (mDimDuration.compareTo(mScreenOffTimeout) > 0) {
+ throw new IllegalArgumentException(
+ "The dim duration cannot be greater than the screen off timeout.");
+ }
+ if (mScreenOffTimeout.compareTo(Duration.ZERO) == 0) {
+ mScreenOffTimeout = INFINITE_TIMEOUT;
+ }
+
if (!Flags.crossDeviceClipboard()) {
mDevicePolicies.delete(POLICY_TYPE_CLIPBOARD);
}
@@ -1213,6 +1327,10 @@ public final class VirtualDeviceParams implements Parcelable {
mDevicePolicies.delete(POLICY_TYPE_CAMERA);
}
+ if (!android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()) {
+ mDevicePolicies.delete(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS);
+ }
+
if (!android.companion.virtualdevice.flags.Flags.activityControlApi()) {
mDevicePolicies.delete(POLICY_TYPE_BLOCKED_ACTIVITY);
}
@@ -1250,7 +1368,9 @@ public final class VirtualDeviceParams implements Parcelable {
mVirtualSensorConfigs,
virtualSensorCallbackDelegate,
mAudioPlaybackSessionId,
- mAudioRecordingSessionId);
+ mAudioRecordingSessionId,
+ mDimDuration.toMillis(),
+ mScreenOffTimeout.toMillis());
}
}
}
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index fc9c94dd5b0f..3e6919bac5fa 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -67,13 +67,6 @@ flag {
}
flag {
- name: "stream_camera"
- namespace: "virtual_devices"
- description: "Enable streaming camera to Virtual Devices"
- bug: "291740640"
-}
-
-flag {
name: "persistent_device_id_api"
is_exported: true
namespace: "virtual_devices"
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
index c2febaeec73a..e8893e47ddc8 100644
--- a/core/java/android/content/res/TEST_MAPPING
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -5,6 +5,9 @@
},
{
"path": "frameworks/base/core/tests/coretests/src/com/android/internal/content/res"
+ },
+ {
+ "path": "platform_testing/libraries/screenshot"
}
],
"presubmit": [
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
index e98fc0c9d02c..26ecbd1982d5 100644
--- a/core/java/android/content/res/flags.aconfig
+++ b/core/java/android/content/res/flags.aconfig
@@ -83,3 +83,15 @@ flag {
bug: "364035303"
}
+flag {
+ name: "system_context_handle_app_info_changed"
+ is_exported: true
+ namespace: "resource_manager"
+ description: "Feature flag for allowing system context to handle application info changes"
+ bug: "362420029"
+ # This flag is read at boot time.
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 41585b3571d6..7d6e7ad857d1 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -215,7 +215,7 @@ final class BulkCursorProxy implements IBulkCursor {
// If close() is being called from the finalizer thread, do not wait for a reply from
// the remote side.
final boolean fromFinalizer =
- android.database.sqlite.Flags.onewayFinalizerClose()
+ android.database.sqlite.Flags.onewayFinalizerCloseFixed()
&& "FinalizerDaemon".equals(Thread.currentThread().getName());
mRemote.transact(CLOSE_TRANSACTION, data, reply,
fromFinalizer ? IBinder.FLAG_ONEWAY: 0);
diff --git a/core/java/android/database/sqlite/flags.aconfig b/core/java/android/database/sqlite/flags.aconfig
index 826b908e6775..d43a66904af8 100644
--- a/core/java/android/database/sqlite/flags.aconfig
+++ b/core/java/android/database/sqlite/flags.aconfig
@@ -2,8 +2,9 @@ package: "android.database.sqlite"
container: "system"
flag {
- name: "oneway_finalizer_close"
+ name: "oneway_finalizer_close_fixed"
namespace: "system_performance"
+ is_fixed_read_only: true
description: "Make BuildCursorNative.close oneway if in the the finalizer"
bug: "368221351"
}
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 611738435f7e..1cd9244333c5 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -420,18 +420,38 @@ public final class DataSpace {
public static final int DATASPACE_HEIF = 4100;
/**
- * ISO/IEC TBD
+ * Ultra HDR
*
- * JPEG image with embedded recovery map following the Jpeg/R specification.
+ * JPEG image with embedded HDR gain map following the Ultra HDR specification and
+ * starting with Android version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM V}
+ * ISO/CD 21496‐1
*
- * <p>This value must always remain aligned with the public ImageFormat Jpeg/R definition and is
- * valid with formats:
- * HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to ISO/IEC TBD.
- * The image contains a standard SDR JPEG and a recovery map. Jpeg/R decoders can use the
- * map to recover the input image.</p>
+ * <p>This value is valid with formats:</p>
+ * <ul>
+ * <li>HAL_PIXEL_FORMAT_BLOB: JPEG image encoded by Jpeg/R encoder according to
+ * ISO/CD 21496‐1</li>
+ * </ul>
+ * <p>
+ * The image contains a standard SDR JPEG and a gain map. Ultra HDR decoders can use the
+ * gain map to boost the brightness of the rendered image.</p>
*/
public static final int DATASPACE_JPEG_R = 4101;
+ /**
+ * ISO/IEC 23008-12:2024
+ *
+ * High Efficiency Image File Format (HEIF) with embedded HDR gain map
+ *
+ * <p>This value is valid with formats:</p>
+ * <ul>
+ * <li>HAL_PIXEL_FORMAT_BLOB: A HEIC image encoded by HEVC encoder
+ * according to ISO/IEC 23008-12:2024 that includes an HDR gain map and
+ * metadata according to ISO/CD 21496‐1.</li>
+ * </ul>
+ */
+ @FlaggedApi(com.android.internal.camera.flags.Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final int DATASPACE_HEIF_ULTRAHDR = 4102;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {
@@ -660,6 +680,7 @@ public final class DataSpace {
DATASPACE_DEPTH,
DATASPACE_DYNAMIC_DEPTH,
DATASPACE_HEIF,
+ DATASPACE_HEIF_ULTRAHDR,
DATASPACE_JPEG_R,
DATASPACE_UNKNOWN,
DATASPACE_SCRGB_LINEAR,
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 16d82caa6c1a..a37648f7e45d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -5775,6 +5775,122 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri
new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
/**
+ * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream).</p>
+ * <p>The configurations are listed as <code>(format, width, height, input?)</code> tuples.</p>
+ * <p>All the static, control, and dynamic metadata tags related to JPEG apply to HEIC formats.
+ * Configuring JPEG and HEIC streams at the same time is not supported.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurations", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for HEIC UltraHDR output formats.</p>
+ * <p>This should correspond to the frame duration when only that
+ * stream is active, with all processing (typically in android.*.mode)
+ * set to either OFF or FAST.</p>
+ * <p>When multiple streams are used in a request, the minimum frame
+ * duration will be max(individual stream min durations).</p>
+ * <p>See {@link CaptureRequest#SENSOR_FRAME_DURATION android.sensor.frameDuration} and
+ * android.scaler.availableStallDurations for more details about
+ * calculating the max frame rate.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @see CaptureRequest#SENSOR_FRAME_DURATION
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for HEIC UltraHDR streams.</p>
+ * <p>A stall duration is how much extra time would get added
+ * to the normal minimum frame duration for a repeating request
+ * that has streams with non-zero stall.</p>
+ * <p>This functions similarly to
+ * android.scaler.availableStallDurations for HEIC UltraHDR
+ * streams.</p>
+ * <p>All HEIC output stream formats may have a nonzero stall
+ * duration.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ * <p><b>Limited capability</b> -
+ * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
+ * {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
+ *
+ * @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurations", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>The available HEIC (ISO/IEC 23008-12/24) UltraHDR stream
+ * configurations that this camera device supports
+ * (i.e. format, width, height, output/input stream) for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.heic.availableHeicStreamConfigurations for details.</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfiguration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfiguration[]>("android.heic.availableHeicUltraHdrStreamConfigurationsMaximumResolution", android.hardware.camera2.params.StreamConfiguration[].class);
+
+ /**
+ * <p>This lists the minimum frame duration for each
+ * format/size combination for HEIC UltraHDR output formats for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.heic.availableHeicMinFrameDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrMinFrameDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
+ * <p>This lists the maximum stall duration for each
+ * output format/size combination for HEIC UltraHDR streams for CaptureRequests where
+ * {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to
+ * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }.</p>
+ * <p>Refer to android.heic.availableHeicStallDurations for details.</p>
+ * <p><b>Units</b>: (format, width, height, ns) x n</p>
+ * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SENSOR_PIXEL_MODE
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final Key<android.hardware.camera2.params.StreamConfigurationDuration[]> HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION =
+ new Key<android.hardware.camera2.params.StreamConfigurationDuration[]>("android.heic.availableHeicUltraHdrStallDurationsMaximumResolution", android.hardware.camera2.params.StreamConfigurationDuration[].class);
+
+ /**
* <p>The direction of the camera faces relative to the vehicle body frame and the
* passenger seats.</p>
* <p>This enum defines the lens facing characteristic of the cameras on the automotive
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index ef7f3f8ab58b..e22c263e893d 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1385,6 +1385,9 @@ public class CameraMetadataNative implements Parcelable {
/*jpegRconfiguration*/ null,
/*jpegRminduration*/ null,
/*jpegRstallduration*/ null,
+ /*heicUltraHDRconfiguration*/ null,
+ /*heicUltraHDRminduration*/ null,
+ /*heicUltraHDRstallduration*/ null,
/*highspeedvideoconfigurations*/ null,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1402,6 +1405,9 @@ public class CameraMetadataNative implements Parcelable {
/*jpegRconfiguration*/ null,
/*jpegRminduration*/ null,
/*jpegRstallduration*/ null,
+ /*heicUltraHDRconfiguration*/ null,
+ /*heicUltraHDRminduration*/ null,
+ /*heicUltraHDRstallduration*/ null,
highSpeedVideoConfigurations,
/*inputoutputformatsmap*/ null, listHighResolution, supportsPrivate[i]);
break;
@@ -1419,6 +1425,9 @@ public class CameraMetadataNative implements Parcelable {
/*jpegRconfiguration*/ null,
/*jpegRminduration*/ null,
/*jpegRstallduration*/ null,
+ /*heicUltraHDRcconfiguration*/ null,
+ /*heicUltraHDRminduration*/ null,
+ /*heicUltraHDRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
inputOutputFormatsMap, listHighResolution, supportsPrivate[i]);
break;
@@ -1436,6 +1445,9 @@ public class CameraMetadataNative implements Parcelable {
/*jpegRconfiguration*/ null,
/*jpegRminduration*/ null,
/*jpegRstallduration*/ null,
+ /*heicUltraHDRcconfiguration*/ null,
+ /*heicUltraHDRminduration*/ null,
+ /*heicUltraHDRstallduration*/ null,
/*highSpeedVideoConfigurations*/ null,
/*inputOutputFormatsMap*/ null, listHighResolution, supportsPrivate[i]);
}
@@ -1607,6 +1619,17 @@ public class CameraMetadataNative implements Parcelable {
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS);
+ StreamConfiguration[] heicUltraHDRConfigurations = null;
+ StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null;
+ StreamConfigurationDuration[] heicUltraHDRStallDurations = null;
+ if (Flags.cameraHeifGainmap()) {
+ heicUltraHDRConfigurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS);
+ heicUltraHDRMinFrameDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS);
+ heicUltraHDRStallDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS);
+ }
StreamConfiguration[] jpegRConfigurations = getBase(
CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS);
StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
@@ -1625,7 +1648,8 @@ public class CameraMetadataNative implements Parcelable {
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
- highSpeedVideoConfigurations, inputOutputFormatsMap,
+ heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+ heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution);
}
@@ -1662,6 +1686,17 @@ public class CameraMetadataNative implements Parcelable {
CameraCharacteristics.HEIC_AVAILABLE_HEIC_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] heicStallDurations = getBase(
CameraCharacteristics.HEIC_AVAILABLE_HEIC_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ StreamConfiguration[] heicUltraHDRConfigurations = null;
+ StreamConfigurationDuration[] heicUltraHDRMinFrameDurations = null;
+ StreamConfigurationDuration[] heicUltraHDRStallDurations = null;
+ if (Flags.cameraHeifGainmap()) {
+ heicUltraHDRConfigurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
+ heicUltraHDRMinFrameDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_MIN_FRAME_DURATIONS_MAXIMUM_RESOLUTION);
+ heicUltraHDRStallDurations = getBase(
+ CameraCharacteristics.HEIC_AVAILABLE_HEIC_ULTRA_HDR_STALL_DURATIONS_MAXIMUM_RESOLUTION);
+ }
StreamConfiguration[] jpegRConfigurations = getBase(
CameraCharacteristics.JPEGR_AVAILABLE_JPEG_R_STREAM_CONFIGURATIONS_MAXIMUM_RESOLUTION);
StreamConfigurationDuration[] jpegRMinFrameDurations = getBase(
@@ -1681,7 +1716,8 @@ public class CameraMetadataNative implements Parcelable {
dynamicDepthStallDurations, heicConfigurations,
heicMinFrameDurations, heicStallDurations,
jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
- highSpeedVideoConfigurations, inputOutputFormatsMap,
+ heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+ heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
listHighResolution, false);
}
diff --git a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
index e3dbb2bbbf90..ec028bf6dcbb 100644
--- a/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
+++ b/core/java/android/hardware/camera2/params/StreamConfigurationMap.java
@@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkArrayElementsNotNull;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
+import android.hardware.DataSpace;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraMetadata;
@@ -31,6 +32,8 @@ import android.util.Size;
import android.util.SparseIntArray;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import java.util.Arrays;
import java.util.HashMap;
import java.util.Objects;
@@ -100,6 +103,12 @@ public final class StreamConfigurationMap {
* {@link StreamConfigurationDuration}
* @param jpegRStallDurations a non-{@code null} array of Jpeg/R
* {@link StreamConfigurationDuration}
+ * @param heicUltraHDRConfigurations a non-{@code null} array of Heic UltraHDR
+ * {@link StreamConfiguration}
+ * @param heicUltraHDRMinFrameDurations a non-{@code null} array of Heic UltraHDR
+ * {@link StreamConfigurationDuration}
+ * @param heicUltraHDRStallDurations a non-{@code null} array of Heic UltraHDR
+ * {@link StreamConfigurationDuration}
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -125,6 +134,9 @@ public final class StreamConfigurationMap {
StreamConfiguration[] jpegRConfigurations,
StreamConfigurationDuration[] jpegRMinFrameDurations,
StreamConfigurationDuration[] jpegRStallDurations,
+ StreamConfiguration[] heicUltraHDRConfigurations,
+ StreamConfigurationDuration[] heicUltraHDRMinFrameDurations,
+ StreamConfigurationDuration[] heicUltraHDRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution) {
@@ -134,8 +146,9 @@ public final class StreamConfigurationMap {
dynamicDepthStallDurations,
heicConfigurations, heicMinFrameDurations, heicStallDurations,
jpegRConfigurations, jpegRMinFrameDurations, jpegRStallDurations,
- highSpeedVideoConfigurations, inputOutputFormatsMap, listHighResolution,
- /*enforceImplementationDefined*/ true);
+ heicUltraHDRConfigurations, heicUltraHDRMinFrameDurations,
+ heicUltraHDRStallDurations, highSpeedVideoConfigurations, inputOutputFormatsMap,
+ listHighResolution, /*enforceImplementationDefined*/ true);
}
/**
@@ -168,6 +181,12 @@ public final class StreamConfigurationMap {
* {@link StreamConfigurationDuration}
* @param jpegRStallDurations a non-{@code null} array of Jpeg/R
* {@link StreamConfigurationDuration}
+ * @param heicUltraHDRConfigurations an array of Heic UltraHDR
+ * {@link StreamConfiguration}, {@code null} if camera doesn't support the format
+ * @param heicUltraHDRMinFrameDurations an array of Heic UltraHDR
+ * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format
+ * @param heicUltraHDRStallDurations an array of Heic UltraHDR
+ * {@link StreamConfigurationDuration}, {@code null} if camera doesn't support the format
* @param highSpeedVideoConfigurations an array of {@link HighSpeedVideoConfiguration}, null if
* camera device does not support high speed video recording
* @param listHighResolution a flag indicating whether the device supports BURST_CAPTURE
@@ -195,6 +214,9 @@ public final class StreamConfigurationMap {
StreamConfiguration[] jpegRConfigurations,
StreamConfigurationDuration[] jpegRMinFrameDurations,
StreamConfigurationDuration[] jpegRStallDurations,
+ StreamConfiguration[] heicUltraHDRConfigurations,
+ StreamConfigurationDuration[] heicUltraHDRMinFrameDurations,
+ StreamConfigurationDuration[] heicUltraHDRStallDurations,
HighSpeedVideoConfiguration[] highSpeedVideoConfigurations,
ReprocessFormatsMap inputOutputFormatsMap,
boolean listHighResolution,
@@ -259,6 +281,18 @@ public final class StreamConfigurationMap {
"heicStallDurations");
}
+ if (heicUltraHDRConfigurations == null || (!Flags.cameraHeifGainmap())) {
+ mHeicUltraHDRConfigurations = new StreamConfiguration[0];
+ mHeicUltraHDRMinFrameDurations = new StreamConfigurationDuration[0];
+ mHeicUltraHDRStallDurations = new StreamConfigurationDuration[0];
+ } else {
+ mHeicUltraHDRConfigurations = checkArrayElementsNotNull(heicUltraHDRConfigurations,
+ "heicUltraHDRConfigurations");
+ mHeicUltraHDRMinFrameDurations = checkArrayElementsNotNull(
+ heicUltraHDRMinFrameDurations, "heicUltraHDRMinFrameDurations");
+ mHeicUltraHDRStallDurations = checkArrayElementsNotNull(heicUltraHDRStallDurations,
+ "heicUltraHDRStallDurations");
+ }
if (jpegRConfigurations == null) {
mJpegRConfigurations = new StreamConfiguration[0];
@@ -336,6 +370,19 @@ public final class StreamConfigurationMap {
mHeicOutputFormats.get(config.getFormat()) + 1);
}
+ if (Flags.cameraHeifGainmap()) {
+ // For each Heic UlrtaHDR format, track how many sizes there are available to configure
+ for (StreamConfiguration config : mHeicUltraHDRConfigurations) {
+ if (!config.isOutput()) {
+ // Ignoring input Heic UltraHDR configs
+ continue;
+ }
+
+ mHeicUltraHDROutputFormats.put(config.getFormat(),
+ mHeicUltraHDROutputFormats.get(config.getFormat()) + 1);
+ }
+ }
+
// For each Jpeg/R format, track how many sizes there are available to configure
for (StreamConfiguration config : mJpegRConfigurations) {
if (!config.isOutput()) {
@@ -483,6 +530,11 @@ public final class StreamConfigurationMap {
int internalFormat = imageFormatToInternal(format);
int dataspace = imageFormatToDataspace(format);
+ if (Flags.cameraHeifGainmap()) {
+ if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ return mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0;
+ }
+ }
if (dataspace == HAL_DATASPACE_DEPTH) {
return mDepthOutputFormats.indexOfKey(internalFormat) >= 0;
} else if (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) {
@@ -607,6 +659,11 @@ public final class StreamConfigurationMap {
surfaceDataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
surfaceDataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
+ if (Flags.cameraHeifGainmap()) {
+ if (surfaceDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ configs = mHeicUltraHDRConfigurations;
+ }
+ }
for (StreamConfiguration config : configs) {
if (config.getFormat() == surfaceFormat && config.isOutput()) {
// Matching format, either need exact size match, or a flexible consumer
@@ -646,6 +703,11 @@ public final class StreamConfigurationMap {
dataspace == HAL_DATASPACE_HEIF ? mHeicConfigurations :
dataspace == HAL_DATASPACE_JPEG_R ? mJpegRConfigurations :
mConfigurations;
+ if (Flags.cameraHeifGainmap()) {
+ if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR ) {
+ configs = mHeicUltraHDRConfigurations;
+ }
+ }
for (StreamConfiguration config : configs) {
if ((config.getFormat() == internalFormat) && config.isOutput() &&
config.getSize().equals(size)) {
@@ -1176,6 +1238,10 @@ public final class StreamConfigurationMap {
Arrays.equals(mHeicConfigurations, other.mHeicConfigurations) &&
Arrays.equals(mHeicMinFrameDurations, other.mHeicMinFrameDurations) &&
Arrays.equals(mHeicStallDurations, other.mHeicStallDurations) &&
+ Arrays.equals(mHeicUltraHDRConfigurations, other.mHeicUltraHDRConfigurations) &&
+ Arrays.equals(mHeicUltraHDRMinFrameDurations,
+ other.mHeicUltraHDRMinFrameDurations) &&
+ Arrays.equals(mHeicUltraHDRStallDurations, other.mHeicUltraHDRStallDurations) &&
Arrays.equals(mJpegRConfigurations, other.mJpegRConfigurations) &&
Arrays.equals(mJpegRMinFrameDurations, other.mJpegRMinFrameDurations) &&
Arrays.equals(mJpegRStallDurations, other.mJpegRStallDurations) &&
@@ -1197,8 +1263,9 @@ public final class StreamConfigurationMap {
mDynamicDepthConfigurations, mDynamicDepthMinFrameDurations,
mDynamicDepthStallDurations, mHeicConfigurations,
mHeicMinFrameDurations, mHeicStallDurations,
- mJpegRConfigurations, mJpegRMinFrameDurations, mJpegRStallDurations,
- mHighSpeedVideoConfigurations);
+ mHeicUltraHDRConfigurations, mHeicUltraHDRMinFrameDurations,
+ mHeicUltraHDRStallDurations, mJpegRConfigurations, mJpegRMinFrameDurations,
+ mJpegRStallDurations, mHighSpeedVideoConfigurations);
}
// Check that the argument is supported by #getOutputFormats or #getInputFormats
@@ -1209,6 +1276,13 @@ public final class StreamConfigurationMap {
int internalDataspace = imageFormatToDataspace(format);
if (output) {
+ if (Flags.cameraHeifGainmap()) {
+ if (internalDataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ if (mHeicUltraHDROutputFormats.indexOfKey(internalFormat) >= 0) {
+ return format;
+ }
+ }
+ }
if (internalDataspace == HAL_DATASPACE_DEPTH) {
if (mDepthOutputFormats.indexOfKey(internalFormat) >= 0) {
return format;
@@ -1429,6 +1503,7 @@ public final class StreamConfigurationMap {
* <li>ImageFormat.DEPTH_POINT_CLOUD => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH_JPEG => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.HEIC => HAL_PIXEL_FORMAT_BLOB
+ * <li>ImageFormat.HEIC_ULTRAHDR => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.JPEG_R => HAL_PIXEL_FORMAT_BLOB
* <li>ImageFormat.DEPTH16 => HAL_PIXEL_FORMAT_Y16
* </ul>
@@ -1451,6 +1526,11 @@ public final class StreamConfigurationMap {
* if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
*/
static int imageFormatToInternal(int format) {
+ if (Flags.cameraHeifGainmap()) {
+ if (format == ImageFormat.HEIC_ULTRAHDR) {
+ return HAL_PIXEL_FORMAT_BLOB;
+ }
+ }
switch (format) {
case ImageFormat.JPEG:
case ImageFormat.DEPTH_POINT_CLOUD:
@@ -1480,6 +1560,7 @@ public final class StreamConfigurationMap {
* <li>ImageFormat.DEPTH16 => HAL_DATASPACE_DEPTH
* <li>ImageFormat.DEPTH_JPEG => HAL_DATASPACE_DYNAMIC_DEPTH
* <li>ImageFormat.HEIC => HAL_DATASPACE_HEIF
+ * <li>ImageFormat.HEIC_ULTRAHDR => DATASPACE_HEIF_ULTRAHDR
* <li>ImageFormat.JPEG_R => HAL_DATASPACE_JPEG_R
* <li>ImageFormat.YUV_420_888 => HAL_DATASPACE_JFIF
* <li>ImageFormat.RAW_SENSOR => HAL_DATASPACE_ARBITRARY
@@ -1508,6 +1589,11 @@ public final class StreamConfigurationMap {
* if {@code format} was {@code HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED}
*/
static int imageFormatToDataspace(int format) {
+ if (Flags.cameraHeifGainmap()) {
+ if (format == ImageFormat.HEIC_ULTRAHDR) {
+ return DataSpace.DATASPACE_HEIF_ULTRAHDR;
+ }
+ }
switch (format) {
case ImageFormat.JPEG:
return HAL_DATASPACE_V0_JFIF;
@@ -1584,13 +1670,21 @@ public final class StreamConfigurationMap {
dataspace == HAL_DATASPACE_JPEG_R ? mJpegROutputFormats :
highRes ? mHighResOutputFormats :
mOutputFormats;
-
+ boolean isDataSpaceHeifUltraHDR = false;
+ if (Flags.cameraHeifGainmap()) {
+ if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ formatsMap = mHeicUltraHDROutputFormats;
+ isDataSpaceHeifUltraHDR = true;
+ }
+ }
int sizesCount = formatsMap.get(format);
if ( ((!output || (dataspace == HAL_DATASPACE_DEPTH || dataspace == HAL_DATASPACE_JPEG_R ||
dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
- dataspace == HAL_DATASPACE_HEIF)) && sizesCount == 0) ||
+ dataspace == HAL_DATASPACE_HEIF ||
+ isDataSpaceHeifUltraHDR)) && sizesCount == 0) ||
(output && (dataspace != HAL_DATASPACE_DEPTH && dataspace != HAL_DATASPACE_JPEG_R &&
dataspace != HAL_DATASPACE_DYNAMIC_DEPTH &&
+ !isDataSpaceHeifUltraHDR &&
dataspace != HAL_DATASPACE_HEIF) &&
mAllOutputFormats.get(format) == 0)) {
return null;
@@ -1604,12 +1698,14 @@ public final class StreamConfigurationMap {
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
(dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
+ (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRConfigurations :
mConfigurations;
StreamConfigurationDuration[] minFrameDurations =
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
(dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
+ (isDataSpaceHeifUltraHDR) ? mHeicUltraHDRMinFrameDurations :
mMinFrameDurations;
for (StreamConfiguration config : configurations) {
@@ -1639,7 +1735,8 @@ public final class StreamConfigurationMap {
// Dynamic depth streams can have both fast and also high res modes.
if ((sizeIndex != sizesCount) && (dataspace == HAL_DATASPACE_DYNAMIC_DEPTH ||
- dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R)) {
+ dataspace == HAL_DATASPACE_HEIF) || (dataspace == HAL_DATASPACE_JPEG_R) ||
+ isDataSpaceHeifUltraHDR) {
if (sizeIndex > sizesCount) {
throw new AssertionError(
@@ -1682,6 +1779,11 @@ public final class StreamConfigurationMap {
if (mHeicOutputFormats.size() > 0) {
formats[i++] = ImageFormat.HEIC;
}
+ if (Flags.cameraHeifGainmap()) {
+ if (mHeicUltraHDROutputFormats.size() > 0) {
+ formats[i++] = ImageFormat.HEIC_ULTRAHDR;
+ }
+ }
if (mJpegROutputFormats.size() > 0) {
formats[i++] = ImageFormat.JPEG_R;
}
@@ -1725,12 +1827,19 @@ public final class StreamConfigurationMap {
* @see #DURATION_STALL
* */
private StreamConfigurationDuration[] getDurations(int duration, int dataspace) {
+ boolean isDataSpaceHeifUltraHDR = false;
+ if (Flags.cameraHeifGainmap()) {
+ if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ isDataSpaceHeifUltraHDR = true;
+ }
+ }
switch (duration) {
case DURATION_MIN_FRAME:
return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ?
mDynamicDepthMinFrameDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicMinFrameDurations :
+ isDataSpaceHeifUltraHDR ? mHeicUltraHDRMinFrameDurations :
(dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRMinFrameDurations :
mMinFrameDurations;
@@ -1738,6 +1847,7 @@ public final class StreamConfigurationMap {
return (dataspace == HAL_DATASPACE_DEPTH) ? mDepthStallDurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthStallDurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicStallDurations :
+ isDataSpaceHeifUltraHDR ? mHeicUltraHDRStallDurations :
(dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRStallDurations :
mStallDurations;
default:
@@ -1754,6 +1864,7 @@ public final class StreamConfigurationMap {
size += mDynamicDepthOutputFormats.size();
size += mHeicOutputFormats.size();
size += mJpegROutputFormats.size();
+ size += mHeicUltraHDROutputFormats.size();
}
return size;
@@ -1774,11 +1885,18 @@ public final class StreamConfigurationMap {
}
private boolean isSupportedInternalConfiguration(int format, int dataspace, Size size) {
+ boolean isDataSpaceHeifUltraHDR = false;
+ if (Flags.cameraHeifGainmap()) {
+ if (dataspace == DataSpace.DATASPACE_HEIF_ULTRAHDR) {
+ isDataSpaceHeifUltraHDR = true;
+ }
+ }
StreamConfiguration[] configurations =
(dataspace == HAL_DATASPACE_DEPTH) ? mDepthConfigurations :
(dataspace == HAL_DATASPACE_DYNAMIC_DEPTH) ? mDynamicDepthConfigurations :
(dataspace == HAL_DATASPACE_HEIF) ? mHeicConfigurations :
(dataspace == HAL_DATASPACE_JPEG_R) ? mJpegRConfigurations :
+ isDataSpaceHeifUltraHDR ? mHeicUltraHDRConfigurations :
mConfigurations;
for (int i = 0; i < configurations.length; i++) {
@@ -1954,6 +2072,11 @@ public final class StreamConfigurationMap {
* @hide
*/
public static String formatToString(int format) {
+ if (Flags.cameraHeifGainmap()) {
+ if (format == ImageFormat.HEIC_ULTRAHDR) {
+ return "HEIC_ULTRAHDR";
+ }
+ }
switch (format) {
case ImageFormat.YV12:
return "YV12";
@@ -2078,6 +2201,10 @@ public final class StreamConfigurationMap {
private final StreamConfigurationDuration[] mHeicMinFrameDurations;
private final StreamConfigurationDuration[] mHeicStallDurations;
+ private final StreamConfiguration[] mHeicUltraHDRConfigurations;
+ private final StreamConfigurationDuration[] mHeicUltraHDRMinFrameDurations;
+ private final StreamConfigurationDuration[] mHeicUltraHDRStallDurations;
+
private final StreamConfiguration[] mJpegRConfigurations;
private final StreamConfigurationDuration[] mJpegRMinFrameDurations;
private final StreamConfigurationDuration[] mJpegRStallDurations;
@@ -2103,6 +2230,8 @@ public final class StreamConfigurationMap {
private final SparseIntArray mDynamicDepthOutputFormats = new SparseIntArray();
/** internal format -> num heic output sizes mapping, for HAL_DATASPACE_HEIF */
private final SparseIntArray mHeicOutputFormats = new SparseIntArray();
+ /** internal format -> num heic output sizes mapping, for DATASPACE_HEIF_GAINMAP */
+ private final SparseIntArray mHeicUltraHDROutputFormats = new SparseIntArray();
/** internal format -> num Jpeg/R output sizes mapping, for HAL_DATASPACE_JPEG_R */
private final SparseIntArray mJpegROutputFormats = new SparseIntArray();
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index 3c6841cd8aeb..56307ae53a0c 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -1432,6 +1432,13 @@ public final class DisplayManagerGlobal {
mExecutor.execute(mCallback::onStopped);
}
}
+
+ @Override // Binder call
+ public void onRequestedBrightnessChanged(float brightness) {
+ if (mCallback != null) {
+ mExecutor.execute(() -> mCallback.onRequestedBrightnessChanged(brightness));
+ }
+ }
}
/**
diff --git a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
index c3490d177be2..9cc0364f2729 100644
--- a/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
+++ b/core/java/android/hardware/display/IVirtualDisplayCallback.aidl
@@ -38,4 +38,9 @@ oneway interface IVirtualDisplayCallback {
* of the application to release() the virtual display.
*/
void onStopped();
+
+ /**
+ * Called when the virtual display's requested brightness has changed.
+ */
+ void onRequestedBrightnessChanged(float brightness);
}
diff --git a/core/java/android/hardware/display/VirtualDisplay.java b/core/java/android/hardware/display/VirtualDisplay.java
index 32b640583734..3b573ea98c27 100644
--- a/core/java/android/hardware/display/VirtualDisplay.java
+++ b/core/java/android/hardware/display/VirtualDisplay.java
@@ -16,6 +16,8 @@
package android.hardware.display;
import android.annotation.FlaggedApi;
+import android.annotation.FloatRange;
+import android.annotation.SystemApi;
import android.view.Display;
import android.view.Surface;
@@ -164,5 +166,25 @@ public final class VirtualDisplay {
* of the application to release() the virtual display.
*/
public void onStopped() { }
+
+ /**
+ * Called when the requested brightness of the display has changed.
+ *
+ * <p>The system may adjust the display's brightness based on user or app activity. This
+ * callback will only be invoked if the display has an explicitly specified default
+ * brightness value.</p>
+ *
+ * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+ * {@code 1.0} indicates the maximum supported brightness.</p>
+ *
+ * @see android.view.View#setKeepScreenOn(boolean)
+ * @see android.view.WindowManager.LayoutParams#screenBrightness
+ * @see VirtualDisplayConfig.Builder#setDefaultBrightness(float)
+ * @hide
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SystemApi
+ public void onRequestedBrightnessChanged(
+ @FloatRange(from = 0.0f, to = 1.0f) float brightness) {}
}
}
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 49944c76eb99..57d9d28a9d47 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -29,6 +29,7 @@ import android.media.projection.MediaProjection;
import android.os.Handler;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.PowerManager;
import android.util.ArraySet;
import android.view.Display;
import android.view.DisplayCutout;
@@ -61,6 +62,7 @@ public final class VirtualDisplayConfig implements Parcelable {
private final boolean mIsHomeSupported;
private final DisplayCutout mDisplayCutout;
private final boolean mIgnoreActivitySizeRestrictions;
+ private final float mDefaultBrightness;
private VirtualDisplayConfig(
@NonNull String name,
@@ -76,7 +78,8 @@ public final class VirtualDisplayConfig implements Parcelable {
float requestedRefreshRate,
boolean isHomeSupported,
@Nullable DisplayCutout displayCutout,
- boolean ignoreActivitySizeRestrictions) {
+ boolean ignoreActivitySizeRestrictions,
+ @FloatRange(from = 0.0f, to = 1.0f) float defaultBrightness) {
mName = name;
mWidth = width;
mHeight = height;
@@ -91,6 +94,7 @@ public final class VirtualDisplayConfig implements Parcelable {
mIsHomeSupported = isHomeSupported;
mDisplayCutout = displayCutout;
mIgnoreActivitySizeRestrictions = ignoreActivitySizeRestrictions;
+ mDefaultBrightness = defaultBrightness;
}
/**
@@ -157,6 +161,22 @@ public final class VirtualDisplayConfig implements Parcelable {
}
/**
+ * Returns the default brightness of the display.
+ *
+ * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of {@code 1.0}
+ * indicates the maximum supported brightness.</p>
+ *
+ * @see Builder#setDefaultBrightness(float)
+ * @hide
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SystemApi
+ public @FloatRange(from = 0.0f, to = 1.0f) float getDefaultBrightness() {
+ return mDefaultBrightness;
+ }
+
+
+ /**
* Returns the unique identifier for the display. Shouldn't be displayed to the user.
* @hide
*/
@@ -245,6 +265,7 @@ public final class VirtualDisplayConfig implements Parcelable {
dest.writeBoolean(mIsHomeSupported);
DisplayCutout.ParcelableWrapper.writeCutoutToParcel(mDisplayCutout, dest, flags);
dest.writeBoolean(mIgnoreActivitySizeRestrictions);
+ dest.writeFloat(mDefaultBrightness);
}
@Override
@@ -272,7 +293,8 @@ public final class VirtualDisplayConfig implements Parcelable {
&& mRequestedRefreshRate == that.mRequestedRefreshRate
&& mIsHomeSupported == that.mIsHomeSupported
&& mIgnoreActivitySizeRestrictions == that.mIgnoreActivitySizeRestrictions
- && Objects.equals(mDisplayCutout, that.mDisplayCutout);
+ && Objects.equals(mDisplayCutout, that.mDisplayCutout)
+ && mDefaultBrightness == that.mDefaultBrightness;
}
@Override
@@ -281,7 +303,7 @@ public final class VirtualDisplayConfig implements Parcelable {
mName, mWidth, mHeight, mDensityDpi, mFlags, mSurface, mUniqueId,
mDisplayIdToMirror, mWindowManagerMirroringEnabled, mDisplayCategories,
mRequestedRefreshRate, mIsHomeSupported, mDisplayCutout,
- mIgnoreActivitySizeRestrictions);
+ mIgnoreActivitySizeRestrictions, mDefaultBrightness);
return hashCode;
}
@@ -303,6 +325,7 @@ public final class VirtualDisplayConfig implements Parcelable {
+ " mIsHomeSupported=" + mIsHomeSupported
+ " mDisplayCutout=" + mDisplayCutout
+ " mIgnoreActivitySizeRestrictions=" + mIgnoreActivitySizeRestrictions
+ + " mDefaultBrightness=" + mDefaultBrightness
+ ")";
}
@@ -321,6 +344,7 @@ public final class VirtualDisplayConfig implements Parcelable {
mIsHomeSupported = in.readBoolean();
mDisplayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(in);
mIgnoreActivitySizeRestrictions = in.readBoolean();
+ mDefaultBrightness = in.readFloat();
}
@NonNull
@@ -355,6 +379,7 @@ public final class VirtualDisplayConfig implements Parcelable {
private boolean mIsHomeSupported = false;
private DisplayCutout mDisplayCutout = null;
private boolean mIgnoreActivitySizeRestrictions = false;
+ private float mDefaultBrightness = 0.0f;
/**
* Creates a new Builder.
@@ -547,6 +572,35 @@ public final class VirtualDisplayConfig implements Parcelable {
}
/**
+ * Sets the default brightness of the display.
+ *
+ * <p>The system will use this brightness value whenever the display should be bright, i.e.
+ * it is powered on and not dimmed due to user activity or app activity.</p>
+ *
+ * <p>Value of {@code 0.0} indicates the minimum supported brightness and value of
+ * {@code 1.0} indicates the maximum supported brightness.</p>
+ *
+ * <p>If unset, defaults to {@code 0.0}</p>
+ *
+ * @see android.view.View#setKeepScreenOn(boolean)
+ * @see Builder#setDefaultBrightness(float)
+ * @see VirtualDisplay.Callback#onRequestedBrightnessChanged(float)
+ * @hide
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SystemApi
+ @NonNull
+ public Builder setDefaultBrightness(@FloatRange(from = 0.0f, to = 1.0f) float brightness) {
+ if (brightness < PowerManager.BRIGHTNESS_MIN
+ || brightness > PowerManager.BRIGHTNESS_MAX) {
+ throw new IllegalArgumentException(
+ "Virtual display default brightness must be in range [0.0, 1.0]");
+ }
+ mDefaultBrightness = brightness;
+ return this;
+ }
+
+ /**
* Builds the {@link VirtualDisplayConfig} instance.
*/
@NonNull
@@ -565,7 +619,8 @@ public final class VirtualDisplayConfig implements Parcelable {
mRequestedRefreshRate,
mIsHomeSupported,
mDisplayCutout,
- mIgnoreActivitySizeRestrictions);
+ mIgnoreActivitySizeRestrictions,
+ mDefaultBrightness);
}
}
}
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index bce95187515a..39dddb723cb9 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -279,4 +279,6 @@ interface IInputManager {
void removeAllCustomInputGestures();
AidlInputGestureData[] getCustomInputGestures();
+
+ AidlInputGestureData[] getAppLaunchBookmarks();
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 876ba1021917..2051dbe7fb2e 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1494,9 +1494,8 @@ public final class InputManager {
try {
return mIm.addCustomInputGesture(inputGestureData.getAidlData());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
- return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
}
/** Removes an existing custom gesture
@@ -1517,9 +1516,8 @@ public final class InputManager {
try {
return mIm.removeCustomInputGesture(inputGestureData.getAidlData());
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
- return CUSTOM_INPUT_GESTURE_RESULT_ERROR_OTHER;
}
/** Removes all custom input gestures
@@ -1534,7 +1532,7 @@ public final class InputManager {
try {
mIm.removeAllCustomInputGestures();
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
}
@@ -1552,12 +1550,32 @@ public final class InputManager {
result.add(new InputGestureData(data));
}
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw e.rethrowFromSystemServer();
}
return result;
}
/**
+ * Return the set of application launch bookmarks handled by the input framework.
+ *
+ * @return list of {@link InputGestureData} containing the application launch shortcuts parsed
+ * at boot time from {@code bookmarks.xml}.
+ *
+ * @hide
+ */
+ public List<InputGestureData> getAppLaunchBookmarks() {
+ try {
+ List<InputGestureData> result = new ArrayList<>();
+ for (AidlInputGestureData data : mIm.getAppLaunchBookmarks()) {
+ result.add(new InputGestureData(data));
+ }
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* A callback used to be notified about battery state changes for an input device. The
* {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
* listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index c4566981d3b5..96f6ad117035 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -30,6 +30,7 @@ import static com.android.hardware.input.Flags.mouseSwapPrimaryButton;
import static com.android.hardware.input.Flags.touchpadTapDragging;
import static com.android.hardware.input.Flags.touchpadThreeFingerTapShortcut;
import static com.android.hardware.input.Flags.touchpadVisualizer;
+import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.input.flags.Flags.FLAG_KEYBOARD_REPEAT_KEYS;
import static com.android.input.flags.Flags.enableInputFilterRustImpl;
import static com.android.input.flags.Flags.keyboardRepeatKeys;
@@ -386,7 +387,7 @@ public class InputSettings {
* @hide
*/
public static boolean isTouchpadThreeFingerTapShortcutFeatureFlagEnabled() {
- return enableCustomizableInputGestures() && touchpadThreeFingerTapShortcut();
+ return isCustomizableInputGesturesFeatureFlagEnabled() && touchpadThreeFingerTapShortcut();
}
/**
@@ -1132,4 +1133,18 @@ public class InputSettings {
Settings.Secure.KEY_REPEAT_DELAY_MS, delayTimeMillis,
UserHandle.USER_CURRENT);
}
+
+ /**
+ * Whether "Customizable key gestures" feature flag is enabled.
+ *
+ * <p>
+ * ‘Customizable key gestures’ is a feature which allows users to customize key based
+ * shortcuts on the physical keyboard.
+ * </p>
+ *
+ * @hide
+ */
+ public static boolean isCustomizableInputGesturesFeatureFlagEnabled() {
+ return enableCustomizableInputGestures() && useKeyGestureEventHandler();
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index c6fd0ee3c80d..85cf9491287c 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -1750,8 +1750,8 @@ public class SoundTrigger {
* internals, typically during enrollment.
* @return the same Builder instance.
*/
- public @NonNull Builder setData(@Nullable byte[] data) {
- mData = data;
+ public @NonNull Builder setData(@NonNull byte[] data) {
+ mData = requireNonNull(data, "Data must not be null");
return this;
}
diff --git a/core/java/android/hardware/usb/OWNERS b/core/java/android/hardware/usb/OWNERS
index a753f9634d0d..37604bc2eb65 100644
--- a/core/java/android/hardware/usb/OWNERS
+++ b/core/java/android/hardware/usb/OWNERS
@@ -1,7 +1,7 @@
# Bug component: 175220
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
+kumarashishg@google.com \ No newline at end of file
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8c3f0ef08039..ae8366817f2b 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -55,6 +55,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING;
+import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API;
import static android.view.inputmethod.Flags.ctrlShiftShortcut;
import static android.view.inputmethod.Flags.predictiveBackIme;
@@ -4392,6 +4393,39 @@ public class InputMethodService extends AbstractInputMethodService {
}
/**
+ * Called when the requested visibility of a custom IME Switcher button changes.
+ *
+ * <p>When the system provides an IME navigation bar, it may decide to show an IME Switcher
+ * button inside this bar. However, the IME can request hiding the bar provided by the system
+ * with {@code getWindowInsetsController().hide(captionBar())} (the IME navigation bar provides
+ * {@link Type#captionBar() captionBar} insets to the IME window). If the request is successful,
+ * then it becomes the IME's responsibility to provide a custom IME Switcher button in its
+ * input view, with equivalent functionality.</p>
+ *
+ * <p>This custom button is only requested to be visible when the system provides the IME
+ * navigation bar, both the bar and the IME Switcher button inside it should be visible,
+ * but the IME successfully requested to hide the bar. This does not depend on the current
+ * visibility of the IME. It could be called with {@code true} while the IME is hidden, in
+ * which case the IME should prepare to show the button as soon as the IME itself is shown.</p>
+ *
+ * <p>This is only called when the requested visibility changes. The default value is
+ * {@code false} and as such, this will not be called initially if the resulting value is
+ * {@code false}.</p>
+ *
+ * <p>This can be called at any time after {@link #onCreate}, even if the IME is not currently
+ * visible. However, this is not guaranteed to be called before the IME is shown, as it depends
+ * on when the IME requested hiding the IME navigation bar. If the request is sent during
+ * the showing flow (e.g. during {@link #onStartInputView}), this will be called shortly after
+ * {@link #onWindowShown}, but before the first IME frame is drawn.</p>
+ *
+ * @param visible whether the button is requested visible or not.
+ */
+ @FlaggedApi(FLAG_IME_SWITCHER_REVAMP_API)
+ public void onCustomImeSwitcherButtonRequestedVisible(boolean visible) {
+ // Intentionally empty
+ }
+
+ /**
* Called when the IME switch button was clicked from the client. Depending on the number of
* enabled IME subtypes, this will either switch to the next IME/subtype, or show the input
* method picker dialog.
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index b08454dd7f8f..38be8d9f772d 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -41,6 +41,7 @@ import android.view.WindowInsets;
import android.view.WindowInsetsController.Appearance;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodManager;
import android.widget.FrameLayout;
@@ -178,6 +179,9 @@ final class NavigationBarController {
private boolean mDrawLegacyNavigationBarBackground;
+ /** Whether a custom IME Switcher button should be visible. */
+ private boolean mCustomImeSwitcherVisible;
+
private final Rect mTempRect = new Rect();
private final int[] mTempPos = new int[2];
@@ -265,6 +269,7 @@ final class NavigationBarController {
// IME navigation bar.
boolean visible = insets.isVisible(captionBar());
mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
+ checkCustomImeSwitcherVisibility();
}
return view.onApplyWindowInsets(insets);
});
@@ -491,6 +496,8 @@ final class NavigationBarController {
mShouldShowImeSwitcherWhenImeIsShown;
mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
+ checkCustomImeSwitcherVisibility();
+
mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
.setImeCaptionBarInsetsHeight(getImeCaptionBarHeight(imeDrawsImeNavBar));
@@ -616,12 +623,33 @@ final class NavigationBarController {
&& mNavigationBarFrame.getVisibility() == View.VISIBLE;
}
+ /**
+ * Checks if a custom IME Switcher button should be visible, and notifies the IME when this
+ * state changes. This can only be {@code true} if three conditions are met:
+ *
+ * <li>The IME should draw the IME navigation bar.</li>
+ * <li>The IME Switcher button should be visible when the IME is visible.</li>
+ * <li>The IME navigation bar should be visible, but was requested hidden by the IME.</li>
+ */
+ private void checkCustomImeSwitcherVisibility() {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return;
+ }
+ final boolean visible = mImeDrawsImeNavBar && mShouldShowImeSwitcherWhenImeIsShown
+ && mNavigationBarFrame != null && !isShown();
+ if (visible != mCustomImeSwitcherVisible) {
+ mCustomImeSwitcherVisible = visible;
+ mService.onCustomImeSwitcherButtonRequestedVisible(mCustomImeSwitcherVisible);
+ }
+ }
+
@Override
public String toDebugString() {
return "{mImeDrawsImeNavBar=" + mImeDrawsImeNavBar
+ " mNavigationBarFrame=" + mNavigationBarFrame
+ " mShouldShowImeSwitcherWhenImeIsShown="
+ mShouldShowImeSwitcherWhenImeIsShown
+ + " mCustomImeSwitcherVisible=" + mCustomImeSwitcherVisible
+ " mAppearance=0x" + Integer.toHexString(mAppearance)
+ " mDarkIntensity=" + mDarkIntensity
+ " mDrawLegacyNavigationBarBackground=" + mDrawLegacyNavigationBarBackground
diff --git a/core/java/android/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 6c3c2852c7e7..5ae425f184c7 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -1315,7 +1315,7 @@ public final class BatteryUsageStats implements Parcelable, Closeable {
}
synchronized (BatteryUsageStats.class) {
- if (!sInstances.isEmpty()) {
+ if (sInstances != null && !sInstances.isEmpty()) {
Exception callSite = sInstances.entrySet().iterator().next().getValue();
int count = sInstances.size();
sInstances.clear();
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 13d7e3c2fbfd..b3aebad1b160 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -40,8 +40,6 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.View;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
@@ -57,10 +55,6 @@ import java.util.stream.Collectors;
*/
@RavenwoodKeepWholeClass
public class Build {
- static {
- // Set up the default system properties.
- RavenwoodEnvironment.ensureRavenwoodInitialized();
- }
private static final String TAG = "Build";
/** Value used for when a build property is unknown. */
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index e80efd2a9380..60eeb2b8b0d5 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -41,7 +41,6 @@ import android.content.ContentResolver;
import android.net.Uri;
import android.os.MessageQueue.OnFileDescriptorEventListener;
import android.ravenwood.annotation.RavenwoodKeepWholeClass;
-import android.ravenwood.annotation.RavenwoodReplace;
import android.ravenwood.annotation.RavenwoodThrow;
import android.system.ErrnoException;
import android.system.Os;
@@ -51,8 +50,6 @@ import android.util.CloseGuard;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.ravenwood.RavenwoodEnvironment;
-
import dalvik.system.VMRuntime;
import libcore.io.IoUtils;
@@ -1254,15 +1251,10 @@ public class ParcelFileDescriptor implements Parcelable, Closeable {
}
}
- @RavenwoodReplace
private static boolean isAtLeastQ() {
return (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q);
}
- private static boolean isAtLeastQ$ravenwood() {
- return RavenwoodEnvironment.workaround().isTargetSdkAtLeastQ();
- }
-
private static int ifAtLeastQ(int value) {
return isAtLeastQ() ? value : 0;
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 71d29af6cf02..e7282435ad46 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -29,6 +29,11 @@ import android.annotation.TestApi;
import android.annotation.UptimeMillisLong;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build.VERSION_CODES;
+import android.ravenwood.annotation.RavenwoodKeep;
+import android.ravenwood.annotation.RavenwoodKeepPartialClass;
+import android.ravenwood.annotation.RavenwoodRedirect;
+import android.ravenwood.annotation.RavenwoodRedirectionClass;
+import android.ravenwood.annotation.RavenwoodReplace;
import android.sysprop.MemoryProperties;
import android.system.ErrnoException;
import android.system.Os;
@@ -37,8 +42,6 @@ import android.system.StructPollfd;
import android.util.Pair;
import android.webkit.WebViewZygote;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.Preconditions;
import com.android.sdksandbox.flags.Flags;
import dalvik.system.VMDebug;
@@ -55,6 +58,8 @@ import java.util.concurrent.TimeoutException;
/**
* Tools for managing OS processes.
*/
+@RavenwoodKeepPartialClass
+@RavenwoodRedirectionClass("Process_ravenwood")
public class Process {
private static final String LOG_TAG = "Process";
@@ -671,7 +676,6 @@ public class Process {
*/
public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();
-
/**
* The process name set via {@link #setArgV0(String)}.
*/
@@ -845,47 +849,20 @@ public class Process {
/**
* Returns true if the current process is a 64-bit runtime.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean is64Bit() {
return VMRuntime.getRuntime().is64Bit();
}
- private static volatile ThreadLocal<SomeArgs> sIdentity$ravenwood;
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void init$ravenwood(final int uid, final int pid) {
- sIdentity$ravenwood = ThreadLocal.withInitial(() -> {
- final SomeArgs args = SomeArgs.obtain();
- args.argi1 = uid;
- args.argi2 = pid;
- args.argi3 = Long.hashCode(Thread.currentThread().getId());
- args.argi4 = THREAD_PRIORITY_DEFAULT;
- args.arg1 = Boolean.TRUE; // backgroundOk
- return args;
- });
- }
-
- /** @hide */
- @android.ravenwood.annotation.RavenwoodKeep
- public static void reset$ravenwood() {
- sIdentity$ravenwood = null;
- }
-
/**
* Returns the identifier of this process, which can be used with
* {@link #killProcess} and {@link #sendSignal}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myPid() {
return Os.getpid();
}
- /** @hide */
- public static final int myPid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi2;
- }
-
/**
* Returns the identifier of this process' parent.
* @hide
@@ -899,39 +876,29 @@ public class Process {
* Returns the identifier of the calling thread, which be used with
* {@link #setThreadPriority(int, int)}.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myTid() {
return Os.gettid();
}
- /** @hide */
- public static final int myTid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi3;
- }
-
/**
* Returns the identifier of this process's uid. This is the kernel uid
* that the process is running under, which is the identity of its
* app-specific sandbox. It is different from {@link #myUserHandle} in that
* a uid identifies a specific app sandbox in a specific user.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodKeep
public static final int myUid() {
return Os.getuid();
}
- /** @hide */
- public static final int myUid$ravenwood() {
- return Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get().argi1;
- }
-
/**
* Returns this process's user handle. This is the
* user the process is running under. It is distinct from
* {@link #myUid()} in that a particular user will have multiple
* distinct apps running under it each with their own uid.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static UserHandle myUserHandle() {
return UserHandle.of(UserHandle.getUserId(myUid()));
}
@@ -940,7 +907,7 @@ public class Process {
* Returns whether the given uid belongs to a system core component or not.
* @hide
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isCoreUid(int uid) {
return UserHandle.isCore(uid);
}
@@ -951,7 +918,7 @@ public class Process {
* @return Whether the uid corresponds to an application sandbox running in
* a specific user.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static boolean isApplicationUid(int uid) {
return UserHandle.isApp(uid);
}
@@ -959,7 +926,7 @@ public class Process {
/**
* Returns whether the current process is in an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated() {
return isIsolated(myUid());
}
@@ -971,7 +938,7 @@ public class Process {
@Deprecated
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
publicAlternatives = "Use {@link #isIsolatedUid(int)} instead.")
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolated(int uid) {
return isIsolatedUid(uid);
}
@@ -979,7 +946,7 @@ public class Process {
/**
* Returns whether the process with the given {@code uid} is an isolated sandbox.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isIsolatedUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_ISOLATED_UID && uid <= LAST_ISOLATED_UID)
@@ -991,7 +958,7 @@ public class Process {
* @see android.app.sdksandbox.SdkSandboxManager
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandboxUid(int uid) {
uid = UserHandle.getAppId(uid);
return (uid >= FIRST_SDK_SANDBOX_UID && uid <= LAST_SDK_SANDBOX_UID);
@@ -1007,7 +974,7 @@ public class Process {
* @throws IllegalArgumentException if input is not an sdk sandbox uid
*/
@SuppressLint("UnflaggedApi") // promoting from @SystemApi.
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getAppUidForSdkSandboxUid(int uid) {
if (!isSdkSandboxUid(uid)) {
throw new IllegalArgumentException("Input UID is not an SDK sandbox UID");
@@ -1023,7 +990,7 @@ public class Process {
*/
@SystemApi(client = MODULE_LIBRARIES)
@TestApi
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
// TODO(b/318651609): Deprecate once Process#getSdkSandboxUidForAppUid is rolled out to 100%
public static final int toSdkSandboxUid(int uid) {
return uid + (FIRST_SDK_SANDBOX_UID - FIRST_APPLICATION_UID);
@@ -1039,7 +1006,7 @@ public class Process {
* @throws IllegalArgumentException if input is not an app uid
*/
@FlaggedApi(Flags.FLAG_SDK_SANDBOX_UID_TO_APP_UID_API)
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final int getSdkSandboxUidForAppUid(int uid) {
if (!isApplicationUid(uid)) {
throw new IllegalArgumentException("Input UID is not an app UID");
@@ -1050,7 +1017,7 @@ public class Process {
/**
* Returns whether the current process is a sdk sandbox process.
*/
- @android.ravenwood.annotation.RavenwoodKeep
+ @RavenwoodKeep
public static final boolean isSdkSandbox() {
return isSdkSandboxUid(myUid());
}
@@ -1127,28 +1094,11 @@ public class Process {
* not have permission to modify the given thread, or to use the given
* priority.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setThreadPriority(int tid,
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int tid, int priority) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- boolean backgroundOk = (args.arg1 == Boolean.TRUE);
- if (priority >= THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
- throw new IllegalArgumentException(
- "Priority " + priority + " blocked by setCanSelfBackground()");
- }
- args.argi4 = priority;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to
* throw an exception if passed a background-level thread priority. This is only
@@ -1156,16 +1106,9 @@ public class Process {
*
* @hide
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
public static final native void setCanSelfBackground(boolean backgroundOk);
- /** @hide */
- public static final void setCanSelfBackground$ravenwood(boolean backgroundOk) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- args.arg1 = Boolean.valueOf(backgroundOk);
- }
-
/**
* Sets the scheduling group for a thread.
* @hide
@@ -1294,13 +1237,12 @@ public class Process {
*
* @see #setThreadPriority(int, int)
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodReplace
public static final native void setThreadPriority(
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST) int priority)
throws IllegalArgumentException, SecurityException;
- /** @hide */
- public static final void setThreadPriority$ravenwood(int priority) {
+ private static void setThreadPriority$ravenwood(int priority) {
setThreadPriority(myTid(), priority);
}
@@ -1317,23 +1259,11 @@ public class Process {
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
*/
- @android.ravenwood.annotation.RavenwoodReplace
+ @RavenwoodRedirect
@IntRange(from = -20, to = THREAD_PRIORITY_LOWEST)
public static final native int getThreadPriority(int tid)
throws IllegalArgumentException;
- /** @hide */
- public static final int getThreadPriority$ravenwood(int tid) {
- final SomeArgs args =
- Preconditions.requireNonNullViaRavenwoodRule(sIdentity$ravenwood).get();
- if (args.argi3 == tid) {
- return args.argi4;
- } else {
- throw new UnsupportedOperationException(
- "Cross-thread priority management not yet available in Ravenwood");
- }
- }
-
/**
* Return the current scheduling policy of a thread, based on Linux.
*
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 90993e1850d4..edeb75b6193d 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -365,7 +365,7 @@ public final class StrictMode {
public static final int NETWORK_POLICY_REJECT = 2;
/**
- * Detect explicit calls to {@link Runtime#gc()}.
+ * Detects explicit calls to {@link Runtime#gc()}.
*/
@ChangeId
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@@ -501,7 +501,7 @@ public final class StrictMode {
private Executor mExecutor;
/**
- * Create a Builder that detects nothing and has no violations. (but note that {@link
+ * Creates a Builder that detects nothing and has no violations. (but note that {@link
* #build} will default to enabling {@link #penaltyLog} if no other penalties are
* specified)
*/
@@ -509,7 +509,7 @@ public final class StrictMode {
mMask = 0;
}
- /** Initialize a Builder from an existing ThreadPolicy. */
+ /** Initializes a Builder from an existing ThreadPolicy. */
public Builder(ThreadPolicy policy) {
mMask = policy.mask;
mListener = policy.mListener;
@@ -517,7 +517,7 @@ public final class StrictMode {
}
/**
- * Detect everything that's potentially suspect.
+ * Detects everything that's potentially suspect.
*
* <p>As of the Gingerbread release this includes network and disk operations but will
* likely expand in future releases.
@@ -544,52 +544,52 @@ public final class StrictMode {
return this;
}
- /** Disable the detection of everything. */
+ /** Disables the detection of everything. */
public @NonNull Builder permitAll() {
return disable(DETECT_THREAD_ALL);
}
- /** Enable detection of network operations. */
+ /** Enables detection of network operations. */
public @NonNull Builder detectNetwork() {
return enable(DETECT_THREAD_NETWORK);
}
- /** Disable detection of network operations. */
+ /** Disables detection of network operations. */
public @NonNull Builder permitNetwork() {
return disable(DETECT_THREAD_NETWORK);
}
- /** Enable detection of disk reads. */
+ /** Enables detection of disk reads. */
public @NonNull Builder detectDiskReads() {
return enable(DETECT_THREAD_DISK_READ);
}
- /** Disable detection of disk reads. */
+ /** Disables detection of disk reads. */
public @NonNull Builder permitDiskReads() {
return disable(DETECT_THREAD_DISK_READ);
}
- /** Enable detection of slow calls. */
+ /** Enables detection of slow calls. */
public @NonNull Builder detectCustomSlowCalls() {
return enable(DETECT_THREAD_CUSTOM);
}
- /** Disable detection of slow calls. */
+ /** Disables detection of slow calls. */
public @NonNull Builder permitCustomSlowCalls() {
return disable(DETECT_THREAD_CUSTOM);
}
- /** Disable detection of mismatches between defined resource types and getter calls. */
+ /** Disables detection of mismatches between defined resource types and getter calls. */
public @NonNull Builder permitResourceMismatches() {
return disable(DETECT_THREAD_RESOURCE_MISMATCH);
}
- /** Detect unbuffered input/output operations. */
+ /** Detects unbuffered input/output operations. */
public @NonNull Builder detectUnbufferedIo() {
return enable(DETECT_THREAD_UNBUFFERED_IO);
}
- /** Disable detection of unbuffered input/output operations. */
+ /** Disables detection of unbuffered input/output operations. */
public @NonNull Builder permitUnbufferedIo() {
return disable(DETECT_THREAD_UNBUFFERED_IO);
}
@@ -610,32 +610,32 @@ public final class StrictMode {
return enable(DETECT_THREAD_RESOURCE_MISMATCH);
}
- /** Enable detection of disk writes. */
+ /** Enables detection of disk writes. */
public @NonNull Builder detectDiskWrites() {
return enable(DETECT_THREAD_DISK_WRITE);
}
- /** Disable detection of disk writes. */
+ /** Disables detection of disk writes. */
public @NonNull Builder permitDiskWrites() {
return disable(DETECT_THREAD_DISK_WRITE);
}
/**
- * Detect calls to {@link Runtime#gc()}.
+ * Detects calls to {@link Runtime#gc()}.
*/
public @NonNull Builder detectExplicitGc() {
return enable(DETECT_THREAD_EXPLICIT_GC);
}
/**
- * Disable detection of calls to {@link Runtime#gc()}.
+ * Disables detection of calls to {@link Runtime#gc()}.
*/
public @NonNull Builder permitExplicitGc() {
return disable(DETECT_THREAD_EXPLICIT_GC);
}
/**
- * Show an annoying dialog to the developer on detected violations, rate-limited to be
+ * Shows an annoying dialog to the developer on detected violations, rate-limited to be
* only a little annoying.
*/
public @NonNull Builder penaltyDialog() {
@@ -643,7 +643,7 @@ public final class StrictMode {
}
/**
- * Crash the whole process on violation. This penalty runs at the end of all enabled
+ * Crashes the whole process on violation. This penalty runs at the end of all enabled
* penalties so you'll still get see logging or other violations before the process
* dies.
*
@@ -655,7 +655,7 @@ public final class StrictMode {
}
/**
- * Crash the whole process on any network usage. Unlike {@link #penaltyDeath}, this
+ * Crashes the whole process on any network usage. Unlike {@link #penaltyDeath}, this
* penalty runs <em>before</em> anything else. You must still have called {@link
* #detectNetwork} to enable this.
*
@@ -665,18 +665,18 @@ public final class StrictMode {
return enable(PENALTY_DEATH_ON_NETWORK);
}
- /** Flash the screen during a violation. */
+ /** Flashes the screen during a violation. */
public @NonNull Builder penaltyFlashScreen() {
return enable(PENALTY_FLASH);
}
- /** Log detected violations to the system log. */
+ /** Logs detected violations to the system log. */
public @NonNull Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data to the {@link
+ * Enables detected violations log a stacktrace and timing data to the {@link
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
* integrators doing beta user field data collection.
*/
@@ -685,7 +685,7 @@ public final class StrictMode {
}
/**
- * Call #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
+ * Calls #{@link OnThreadViolationListener#onThreadViolation(Violation)} on specified
* executor every violation.
*/
public @NonNull Builder penaltyListener(
@@ -715,7 +715,7 @@ public final class StrictMode {
}
/**
- * Construct the ThreadPolicy instance.
+ * Constructs the ThreadPolicy instance.
*
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
* #penaltyLog} is implicitly set.
@@ -805,7 +805,7 @@ public final class StrictMode {
mMask = 0;
}
- /** Build upon an existing VmPolicy. */
+ /** Builds upon an existing VmPolicy. */
public Builder(VmPolicy base) {
mMask = base.mask;
mClassInstanceLimitNeedCow = true;
@@ -815,7 +815,7 @@ public final class StrictMode {
}
/**
- * Set an upper bound on how many instances of a class can be in memory at once. Helps
+ * Sets an upper bound on how many instances of a class can be in memory at once. Helps
* to prevent object leaks.
*/
public @NonNull Builder setClassInstanceLimit(Class klass, int instanceLimit) {
@@ -838,7 +838,7 @@ public final class StrictMode {
return this;
}
- /** Detect leaks of {@link android.app.Activity} subclasses. */
+ /** Detects leaks of {@link android.app.Activity} subclasses. */
public @NonNull Builder detectActivityLeaks() {
return enable(DETECT_VM_ACTIVITY_LEAKS);
}
@@ -852,7 +852,7 @@ public final class StrictMode {
}
/**
- * Detect reflective usage of APIs that are not part of the public Android SDK.
+ * Detects reflective usage of APIs that are not part of the public Android SDK.
*
* <p>Note that any non-SDK APIs that this processes accesses before this detection is
* enabled may not be detected. To ensure that all such API accesses are detected,
@@ -863,7 +863,7 @@ public final class StrictMode {
}
/**
- * Permit reflective usage of APIs that are not part of the public Android SDK. Note
+ * Permits reflective usage of APIs that are not part of the public Android SDK. Note
* that this <b>only</b> affects {@code StrictMode}, the underlying runtime may
* continue to restrict or warn on access to methods that are not part of the
* public SDK.
@@ -873,7 +873,7 @@ public final class StrictMode {
}
/**
- * Detect everything that's potentially suspect.
+ * Detects everything that's potentially suspect.
*
* <p>In the Honeycomb release this includes leaks of SQLite cursors, Activities, and
* other closable objects but will likely expand in future releases.
@@ -924,8 +924,8 @@ public final class StrictMode {
}
/**
- * Detect when an {@link android.database.sqlite.SQLiteCursor} or other SQLite object is
- * finalized without having been closed.
+ * Detects when an {@link android.database.sqlite.SQLiteCursor} or other SQLite
+ * object is finalized without having been closed.
*
* <p>You always want to explicitly close your SQLite cursors to avoid unnecessary
* database contention and temporary memory leaks.
@@ -935,8 +935,8 @@ public final class StrictMode {
}
/**
- * Detect when an {@link java.io.Closeable} or other object with an explicit termination
- * method is finalized without having been closed.
+ * Detects when an {@link java.io.Closeable} or other object with an explicit
+ * termination method is finalized without having been closed.
*
* <p>You always want to explicitly close such objects to avoid unnecessary resources
* leaks.
@@ -946,16 +946,16 @@ public final class StrictMode {
}
/**
- * Detect when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked during
- * {@link Context} teardown.
+ * Detects when a {@link BroadcastReceiver} or {@link ServiceConnection} is leaked
+ * during {@link Context} teardown.
*/
public @NonNull Builder detectLeakedRegistrationObjects() {
return enable(DETECT_VM_REGISTRATION_LEAKS);
}
/**
- * Detect when the calling application exposes a {@code file://} {@link android.net.Uri}
- * to another app.
+ * Detects when the calling application exposes a {@code file://}
+ * {@link android.net.Uri} to another app.
*
* <p>This exposure is discouraged since the receiving app may not have access to the
* shared path. For example, the receiving app may not have requested the {@link
@@ -973,9 +973,9 @@ public final class StrictMode {
}
/**
- * Detect any network traffic from the calling app which is not wrapped in SSL/TLS. This
- * can help you detect places that your app is inadvertently sending cleartext data
- * across the network.
+ * Detects any network traffic from the calling app which is not wrapped in SSL/TLS.
+ * This can help you detect places that your app is inadvertently sending cleartext
+ * data across the network.
*
* <p>Using {@link #penaltyDeath()} or {@link #penaltyDeathOnCleartextNetwork()} will
* block further traffic on that socket to prevent accidental data leakage, in addition
@@ -992,7 +992,7 @@ public final class StrictMode {
}
/**
- * Detect when the calling application sends a {@code content://} {@link
+ * Detects when the calling application sends a {@code content://} {@link
* android.net.Uri} to another app without setting {@link
* Intent#FLAG_GRANT_READ_URI_PERMISSION} or {@link
* Intent#FLAG_GRANT_WRITE_URI_PERMISSION}.
@@ -1008,7 +1008,7 @@ public final class StrictMode {
}
/**
- * Detect any sockets in the calling app which have not been tagged using {@link
+ * Detects any sockets in the calling app which have not been tagged using {@link
* TrafficStats}. Tagging sockets can help you investigate network usage inside your
* app, such as a narrowing down heavy usage to a specific library or component.
*
@@ -1028,7 +1028,7 @@ public final class StrictMode {
}
/**
- * Detect any implicit reliance on Direct Boot automatic filtering
+ * Detects any implicit reliance on Direct Boot automatic filtering
* of {@link PackageManager} values. Violations are only triggered
* when implicit calls are made while the user is locked.
* <p>
@@ -1051,7 +1051,7 @@ public final class StrictMode {
}
/**
- * Detect access to filesystem paths stored in credential protected
+ * Detects access to filesystem paths stored in credential protected
* storage areas while the user is locked.
* <p>
* When a user is locked, credential protected storage is
@@ -1072,7 +1072,7 @@ public final class StrictMode {
}
/**
- * Detect attempts to invoke a method on a {@link Context} that is not suited for such
+ * Detects attempts to invoke a method on a {@link Context} that is not suited for such
* operation.
* <p>An example of this is trying to obtain an instance of UI service (e.g.
* {@link android.view.WindowManager}) from a non-visual {@link Context}. This is not
@@ -1086,7 +1086,7 @@ public final class StrictMode {
}
/**
- * Disable detection of incorrect context use.
+ * Disables detection of incorrect context use.
*
* @see #detectIncorrectContextUse()
*
@@ -1098,7 +1098,7 @@ public final class StrictMode {
}
/**
- * Detect when your app sends an unsafe {@link Intent}.
+ * Detects when your app sends an unsafe {@link Intent}.
* <p>
* Violations may indicate security vulnerabilities in the design of
* your app, where a malicious app could trick you into granting
@@ -1139,7 +1139,7 @@ public final class StrictMode {
}
/**
- * Permit your app to launch any {@link Intent} which originated
+ * Permits your app to launch any {@link Intent} which originated
* from outside your app.
* <p>
* Disabling this check is <em>strongly discouraged</em>, as
@@ -1214,13 +1214,13 @@ public final class StrictMode {
return enable(PENALTY_DEATH_ON_FILE_URI_EXPOSURE);
}
- /** Log detected violations to the system log. */
+ /** Logs detected violations to the system log. */
public @NonNull Builder penaltyLog() {
return enable(PENALTY_LOG);
}
/**
- * Enable detected violations log a stacktrace and timing data to the {@link
+ * Enables detected violations log a stacktrace and timing data to the {@link
* android.os.DropBoxManager DropBox} on policy violation. Intended mostly for platform
* integrators doing beta user field data collection.
*/
@@ -1229,7 +1229,7 @@ public final class StrictMode {
}
/**
- * Call #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
+ * Calls #{@link OnVmViolationListener#onVmViolation(Violation)} on every violation.
*/
public @NonNull Builder penaltyListener(
@NonNull Executor executor, @NonNull OnVmViolationListener listener) {
@@ -1258,7 +1258,7 @@ public final class StrictMode {
}
/**
- * Construct the VmPolicy instance.
+ * Constructs the VmPolicy instance.
*
* <p>Note: if no penalties are enabled before calling <code>build</code>, {@link
* #penaltyLog} is implicitly set.
@@ -1474,7 +1474,7 @@ public final class StrictMode {
}
/**
- * Determine if the given app is "bundled" as part of the system image. These bundled apps are
+ * Determines if the given app is "bundled" as part of the system image. These bundled apps are
* developed in lock-step with the OS, and they aren't updated outside of an OTA, so we want to
* chase any {@link StrictMode} regressions by enabling detection when running on {@link
* Build#IS_USERDEBUG} or {@link Build#IS_ENG} builds.
@@ -1512,7 +1512,7 @@ public final class StrictMode {
}
/**
- * Initialize default {@link ThreadPolicy} for the current thread.
+ * Initializes default {@link ThreadPolicy} for the current thread.
*
* @hide
*/
@@ -1547,7 +1547,7 @@ public final class StrictMode {
}
/**
- * Initialize default {@link VmPolicy} for the current VM.
+ * Initializes default {@link VmPolicy} for the current VM.
*
* @hide
*/
@@ -2244,7 +2244,7 @@ public final class StrictMode {
}
/**
- * Enable the recommended StrictMode defaults, with violations just being logged.
+ * Enables the recommended StrictMode defaults, with violations just being logged.
*
* <p>This catches disk and network access on the main thread, as well as leaked SQLite cursors
* and unclosed resources. This is simply a wrapper around {@link #setVmPolicy} and {@link
@@ -2545,7 +2545,7 @@ public final class StrictMode {
private static final SparseLongArray sRealLastVmViolationTime = new SparseLongArray();
/**
- * Clamp the given map by removing elements with timestamp older than the given retainSince.
+ * Clamps the given map by removing elements with timestamp older than the given retainSince.
*/
private static void clampViolationTimeMap(final @NonNull SparseLongArray violationTime,
final long retainSince) {
@@ -2812,7 +2812,7 @@ public final class StrictMode {
};
/**
- * Enter a named critical span (e.g. an animation)
+ * Enters a named critical span (e.g. an animation)
*
* <p>The name is an arbitary label (or tag) that will be applied to any strictmode violation
* that happens while this span is active. You must call finish() on the span when done.
@@ -3056,7 +3056,7 @@ public final class StrictMode {
/** If this is a instance count violation, the number of instances in memory, else -1. */
public long numInstances = -1;
- /** Create an instance of ViolationInfo initialized from an exception. */
+ /** Creates an instance of ViolationInfo initialized from an exception. */
ViolationInfo(Violation tr, int penaltyMask) {
this.mViolation = tr;
this.mPenaltyMask = penaltyMask;
@@ -3131,8 +3131,8 @@ public final class StrictMode {
}
/**
- * Add a {@link Throwable} from the current process that caused the underlying violation. We
- * only preserve the stack trace elements.
+ * Adds a {@link Throwable} from the current process that caused the underlying violation.
+ * We only preserve the stack trace elements.
*
* @hide
*/
@@ -3160,14 +3160,14 @@ public final class StrictMode {
return result;
}
- /** Create an instance of ViolationInfo initialized from a Parcel. */
+ /** Creates an instance of ViolationInfo initialized from a Parcel. */
@UnsupportedAppUsage
public ViolationInfo(Parcel in) {
this(in, false);
}
/**
- * Create an instance of ViolationInfo initialized from a Parcel.
+ * Creates an instance of ViolationInfo initialized from a Parcel.
*
* @param unsetGatheringBit if true, the caller is the root caller and the gathering penalty
* should be removed.
@@ -3203,7 +3203,7 @@ public final class StrictMode {
tags = in.readStringArray();
}
- /** Save a ViolationInfo instance to a parcel. */
+ /** Saves a ViolationInfo instance to a parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeSerializable(mViolation);
@@ -3248,7 +3248,7 @@ public final class StrictMode {
}
}
- /** Dump a ViolationInfo instance to a Printer. */
+ /** Dumps a ViolationInfo instance to a Printer. */
public void dump(Printer pw, String prefix) {
pw.println(prefix + "stackTrace: " + getStackTrace());
pw.println(prefix + "penalty: " + mPenaltyMask);
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 6a4932211f27..ea9997b8e5f4 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -53,6 +53,24 @@ flag {
}
flag {
+ name: "enhanced_confirmation_in_call_apis_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "enable enhanced confirmation incall apis"
+ bug: "310220212"
+}
+
+flag {
+ name: "unknown_call_package_install_blocking_enabled"
+ is_exported: true
+ is_fixed_read_only: true
+ namespace: "permissions"
+ description: "enable the blocking of certain app installs during an unknown call"
+ bug: "310220212"
+}
+
+flag {
name: "op_enable_mobile_data_by_user"
is_exported: true
namespace: "permissions"
@@ -332,3 +350,20 @@ flag {
description: "Enables ExtServices to leverage TextClassifier for OTP detection"
bug: "351976749"
}
+
+flag {
+ name: "health_connect_backup_restore_permission_enabled"
+ is_fixed_read_only: true
+ namespace: "health_connect"
+ description: "This flag protects the permission that is required to call Health Connect backup and restore apis"
+ bug: "376014879" # android_fr bug
+}
+
+flag {
+ name: "enable_aiai_proxied_text_classifiers"
+ is_fixed_read_only: true
+ is_exported: true
+ namespace: "permissions"
+ description: "Enables the AiAi to utilize the default OTP text classifier that is also used by ExtServices"
+ bug: "377229653"
+}
diff --git a/core/java/android/print/OWNERS b/core/java/android/print/OWNERS
index 0809de25b45c..ce79f5d0c669 100644
--- a/core/java/android/print/OWNERS
+++ b/core/java/android/print/OWNERS
@@ -2,3 +2,4 @@
anothermark@google.com
kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/printservice/OWNERS b/core/java/android/printservice/OWNERS
index 0809de25b45c..ce79f5d0c669 100644
--- a/core/java/android/printservice/OWNERS
+++ b/core/java/android/printservice/OWNERS
@@ -2,3 +2,4 @@
anothermark@google.com
kumarashishg@google.com
+bmgordon@google.com
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index 2e660fc1f157..7d79fd3d44ea 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -1164,7 +1164,7 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
public boolean startRecognition(@RecognitionFlags int recognitionFlags) {
if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")");
synchronized (mLock) {
- return startRecognitionLocked(recognitionFlags, null /* data */) == STATUS_OK;
+ return startRecognitionLocked(recognitionFlags, /* data= */new byte[0]) == STATUS_OK;
}
}
@@ -1496,8 +1496,8 @@ public class AlwaysOnHotwordDetector extends AbstractDetector {
}
@GuardedBy("mLock")
- private int startRecognitionLocked(int recognitionFlags,
- @Nullable byte[] data) {
+ @SuppressWarnings("FlaggedApi") // RecognitionConfig.Builder is available internally.
+ private int startRecognitionLocked(int recognitionFlags, @NonNull byte[] data) {
if (DBG) {
Slog.d(TAG, "startRecognition("
+ recognitionFlags
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 1fe06d474803..66d64d711e58 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -176,7 +176,7 @@ public class HapticFeedbackConstants {
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* passed the threshold for the action to be activated on release.
*
@@ -186,7 +186,7 @@ public class HapticFeedbackConstants {
/**
* The user is executing a swipe/drag-style gesture, such as pull-to-refresh, where the
- * gesture action is “eligible” at a certain threshold of movement, and can be cancelled by
+ * gesture action is "eligible" at a certain threshold of movement, and can be cancelled by
* moving back past the threshold. This constant indicates that the user's motion has just
* re-crossed back "under" the threshold for the action to be activated, meaning the gesture is
* currently in a cancelled state.
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 78773529294a..acbd95bf6810 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -212,8 +212,7 @@ public class InsetsSourceControl implements Parcelable {
&& mInitiallyVisible == that.mInitiallyVisible
&& mSurfacePosition.equals(that.mSurfacePosition)
&& mInsetsHint.equals(that.mInsetsHint)
- && mSkipAnimationOnce == that.mSkipAnimationOnce
- && Objects.equals(mImeStatsToken, that.mImeStatsToken);
+ && mSkipAnimationOnce == that.mSkipAnimationOnce;
}
@Override
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b8b22e283175..df54d310059f 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -312,6 +312,7 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeNotifyShutdown();
private static native void nativeSetLuts(long transactionObj, long nativeObject,
float[] buffers, int[] slots, int[] dimensions, int[] sizes, int[] samplingKeys);
+ private static native void nativeEnableDebugLogCallPoints(long transactionObj);
/**
* Transforms that can be applied to buffers as they are displayed to a window.
@@ -4605,7 +4606,6 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * TODO(b/366484871): To be removed once we have some logging in native
* This is called when BlastBufferQueue.mergeWithNextTransaction() is called from java, and
* for the purposes of logging that path.
*/
@@ -4616,6 +4616,7 @@ public final class SurfaceControl implements Parcelable {
if (mCalls != null) {
mCalls.clear();
}
+ nativeEnableDebugLogCallPoints(mNativeObject);
}
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e49eec69fff5..5ee229f87a1e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -30,6 +30,7 @@ import static android.view.Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
import static android.view.Surface.FRAME_RATE_COMPATIBILITY_GTE;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+import static android.view.accessibility.Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS;
import static android.view.accessibility.Flags.FLAG_SUPPLEMENTAL_DESCRIPTION;
import static android.view.accessibility.Flags.removeChildHoverCheckForTouchExploration;
import static android.view.accessibility.Flags.supplementalDescription;
@@ -42,6 +43,7 @@ import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_H
import static android.view.flags.Flags.FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.calculateBoundsInParentFromBoundsInScreen;
import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.view.flags.Flags.toolkitFrameRateAnimationBugfix25q1;
@@ -970,6 +972,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private static boolean sAlwaysRemeasureExactly = false;
/**
+ * When true calculates the bounds in parent from bounds in screen relative to its parents.
+ * This addresses the deprecated API (setBoundsInParent) in Compose, which causes empty
+ * getBoundsInParent call for Compose apps.
+ */
+ private static boolean sCalculateBoundsInParentFromBoundsInScreenFlagValue = false;
+
+ /**
* When true makes it possible to use onMeasure caches also when the force layout flag is
* enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
*/
@@ -2561,6 +2570,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+ sCalculateBoundsInParentFromBoundsInScreenFlagValue =
+ calculateBoundsInParentFromBoundsInScreen();
sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
}
@@ -8941,44 +8952,45 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/**
- * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT}
- * {@link AccessibilityEvent} to suggest that an accessibility service announce the
- * specified text to its users.
- * <p>
- * Note: The event generated with this API carries no semantic meaning, and is appropriate only
- * in exceptional situations. Apps can generally achieve correct behavior for accessibility by
- * accurately supplying the semantics of their UI.
- * They should not need to specify what exactly is announced to users.
+ * Convenience method for sending a {@link AccessibilityEvent#TYPE_ANNOUNCEMENT} {@link
+ * AccessibilityEvent} to suggest that an accessibility service announce the specified text to
+ * its users.
*
- * <p>
- * In general, only announce transitions and don't generate a confirmation message for simple
- * actions like a button press. Label your controls concisely and precisely instead, and for
- * significant UI changes like window changes, use
- * {@link android.app.Activity#setTitle(CharSequence)} and
- * {@link #setAccessibilityPaneTitle(CharSequence)}.
+ * <p>Note: The event generated with this API carries no semantic meaning, and accessibility
+ * services may choose to ignore it. Apps that accurately supply accessibility with the
+ * semantics of their UI should not need to specify what exactly is announced.
*
- * <p>
- * Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
+ * <p>In general, do not attempt to generate announcements as confirmation message for simple
+ * actions like a button press. Label your controls concisely and precisely instead.
+ *
+ * <p>To convey significant UI changes like window changes, use {@link
+ * android.app.Activity#setTitle(CharSequence)} and {@link
+ * #setAccessibilityPaneTitle(CharSequence)}.
+ *
+ * <p>Use {@link #setAccessibilityLiveRegion(int)} to inform the user of changes to critical
* views within the user interface. These should still be used sparingly as they may generate
* announcements every time a View is updated.
*
- * <p>
- * For notifying users about errors, such as in a login screen with text that displays an
- * "incorrect password" notification, that view should send an AccessibilityEvent of type
- * {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR} and set
- * {@link AccessibilityNodeInfo#setError(CharSequence)} instead. Custom widgets should expose
- * error-setting methods that support accessibility automatically. For example, instead of
- * explicitly sending this event when using a TextView, use
- * {@link android.widget.TextView#setError(CharSequence)}.
- *
- * <p>
- * Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
+ * <p>Use {@link #setStateDescription(CharSequence)} to convey state changes to views within the
* user interface. While a live region may send different types of events generated by the view,
* state description will send {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} events of
* type {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_STATE_DESCRIPTION}.
*
+ * <p>For notifying users about errors, such as in a login screen with text that displays an
+ * "incorrect password" notification, set {@link AccessibilityNodeInfo#setError(CharSequence)}
+ * and dispatch an {@link AccessibilityEvent#TYPE_WINDOW_CONTENT_CHANGED} event with a change
+ * type of {@link AccessibilityEvent#CONTENT_CHANGE_TYPE_ERROR}, instead. Some widgets may
+ * expose methods that convey error states to accessibility automatically, such as {@link
+ * android.widget.TextView#setError(CharSequence)}, which manages these accessibility semantics
+ * and event dispatch for callers.
+ *
+ * @deprecated Use one of the methods described in the documentation above to semantically
+ * describe UI instead of using an announcement, as accessibility services may choose to
+ * ignore events dispatched with this method.
* @param text The announcement text.
*/
+ @FlaggedApi(FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public void announceForAccessibility(CharSequence text) {
if (AccessibilityManager.getInstance(mContext).isEnabled() && mParent != null) {
AccessibilityEvent event = AccessibilityEvent.obtain(
@@ -9806,7 +9818,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
structure.setChildCount(1);
final ViewStructure root = structure.newChild(0);
if (info != null) {
- populateVirtualStructure(root, provider, info, forAutofill);
+ populateVirtualStructure(root, provider, info, null, forAutofill);
info.recycle();
} else {
Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
@@ -11105,11 +11117,19 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
private void populateVirtualStructure(ViewStructure structure,
AccessibilityNodeProvider provider, AccessibilityNodeInfo info,
- boolean forAutofill) {
+ @Nullable AccessibilityNodeInfo parentInfo, boolean forAutofill) {
structure.setId(AccessibilityNodeInfo.getVirtualDescendantId(info.getSourceNodeId()),
null, null, info.getViewIdResourceName());
Rect rect = structure.getTempRect();
- info.getBoundsInParent(rect);
+ // The bounds in parent for Jetpack Compose views aren't set as setBoundsInParent is
+ // deprecated, and only setBoundsInScreen is called.
+ // The bounds in parent can be calculated by diff'ing the child view's bounds in screen with
+ // the parent's.
+ if (sCalculateBoundsInParentFromBoundsInScreenFlagValue) {
+ getBoundsInParent(info, parentInfo, rect);
+ } else {
+ info.getBoundsInParent(rect);
+ }
structure.setDimens(rect.left, rect.top, 0, 0, rect.width(), rect.height());
structure.setVisibility(VISIBLE);
structure.setEnabled(info.isEnabled());
@@ -11193,13 +11213,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityNodeInfo.getVirtualDescendantId(info.getChildId(i)));
if (cinfo != null) {
ViewStructure child = structure.newChild(i);
- populateVirtualStructure(child, provider, cinfo, forAutofill);
+ populateVirtualStructure(child, provider, cinfo, info, forAutofill);
cinfo.recycle();
}
}
}
}
+ private void getBoundsInParent(@NonNull AccessibilityNodeInfo info,
+ @Nullable AccessibilityNodeInfo parentInfo, @NonNull Rect rect) {
+ info.getBoundsInParent(rect);
+ // Fallback to calculate bounds in parent by diffing the bounds in
+ // screen if it's all 0.
+ if ((rect.left | rect.top | rect.right | rect.bottom) == 0) {
+ if (parentInfo != null) {
+ Rect parentBoundsInScreen = parentInfo.getBoundsInScreen();
+ Rect boundsInScreen = info.getBoundsInScreen();
+ rect.set(boundsInScreen.left - parentBoundsInScreen.left,
+ boundsInScreen.top - parentBoundsInScreen.top,
+ boundsInScreen.right - parentBoundsInScreen.left,
+ boundsInScreen.bottom - parentBoundsInScreen.top);
+ } else {
+ info.getBoundsInScreen(rect);
+ }
+ }
+ }
+
/**
* Dispatch creation of {@link ViewStructure} down the hierarchy. The default
* implementation calls {@link #onProvideStructure} and
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 5b39f62db261..b4b0687eb498 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1426,6 +1426,31 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
/**
+ * Activity-level {@link android.content.pm.PackageManager.Property PackageManager.Property}
+ * that specifies whether this activity can declare or request
+ * {@link android.R.attr#screenOrientation fixed orientation},
+ * {@link android.R.attr#minAspectRatio max aspect ratio},
+ * {@link android.R.attr#maxAspectRatio min aspect ratio}
+ * {@link android.R.attr#resizeableActivity unresizable} on large screen devices with the
+ * ignore orientation request display setting enabled since Android 16 (API level 36) or higher.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;activity&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY"
+ * android:value="true"/&gt;
+ * &lt;/activity&gt;
+ * </pre>
+ * @hide
+ */
+ // TODO(b/357141415): Make this public API.
+ String PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY =
+ "android.window.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY";
+
+ /**
* @hide
*/
public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index c690787e4a33..0dfaf4149ce5 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -563,10 +563,13 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Represents the event of an application making an announcement.
- * <p>
- * In general, follow the practices described in
- * {@link View#announceForAccessibility(CharSequence)}.
+ *
+ * @deprecated Use one of the semantic alternative methods described in the documentation of
+ * {@link View#announceForAccessibility(CharSequence)} instead of using this event, as
+ * accessibility services may choose to ignore dispatch of this event type.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ACCESSIBILITY_ANNOUNCEMENT_APIS)
+ @Deprecated
public static final int TYPE_ANNOUNCEMENT = 1 << 14;
/**
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c07da410c7f9..7177ef330f06 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -85,6 +85,13 @@ flag {
flag {
namespace: "accessibility"
+ name: "deprecate_accessibility_announcement_apis"
+ description: "Controls the deprecation of platform APIs related to disruptive accessibility announcements"
+ bug: "376727542"
+}
+
+flag {
+ namespace: "accessibility"
name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 2ca62a0725df..dd32d57bd650 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -221,6 +221,7 @@ public interface ImeTracker {
PHASE_WM_INVOKING_IME_REQUESTED_LISTENER,
PHASE_CLIENT_ALREADY_HIDDEN,
PHASE_CLIENT_VIEW_HANDLER_AVAILABLE,
+ PHASE_SERVER_UPDATE_CLIENT_VISIBILITY,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
@@ -430,6 +431,11 @@ public interface ImeTracker {
* continue without.
*/
int PHASE_CLIENT_VIEW_HANDLER_AVAILABLE = ImeProtoEnums.PHASE_CLIENT_VIEW_HANDLER_AVAILABLE;
+ /**
+ * ImeInsetsSourceProvider sets the reported visibility of the caller/client window (either the
+ * app or the RemoteInsetsControlTarget).
+ */
+ int PHASE_SERVER_UPDATE_CLIENT_VISIBILITY = ImeProtoEnums.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY;
/**
* Called when an IME request is started.
diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java
index be91cfb9ebf8..a67ae7c96a54 100644
--- a/core/java/android/view/inputmethod/InputMethodSubtype.java
+++ b/core/java/android/view/inputmethod/InputMethodSubtype.java
@@ -17,8 +17,10 @@
package android.view.inputmethod;
import android.annotation.AnyThread;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.StringRes;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -87,8 +89,17 @@ public final class InputMethodSubtype implements Parcelable {
private final boolean mIsAsciiCapable;
private final int mSubtypeHashCode;
private final int mSubtypeIconResId;
+ /** The subtype name resource identifier. */
private final int mSubtypeNameResId;
+ /** The untranslatable name of the subtype. */
+ @NonNull
private final CharSequence mSubtypeNameOverride;
+ /** The layout label string resource identifier. */
+ @StringRes
+ private final int mLayoutLabelResId;
+ /** The non-localized layout label. */
+ @NonNull
+ private final CharSequence mLayoutLabelNonLocalized;
private final String mPkLanguageTag;
private final String mPkLayoutType;
private final int mSubtypeId;
@@ -176,6 +187,7 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeNameResId = subtypeNameResId;
return this;
}
+ /** The subtype name resource identifier. */
private int mSubtypeNameResId = 0;
/**
@@ -191,9 +203,56 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeNameOverride = nameOverride;
return this;
}
+ /** The untranslatable name of the subtype. */
+ @NonNull
private CharSequence mSubtypeNameOverride = "";
/**
+ * Sets the layout label string resource identifier.
+ *
+ * @param layoutLabelResId the layout label string resource identifier.
+ *
+ * @see #getLayoutDisplayName
+ */
+ @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+ @NonNull
+ public InputMethodSubtypeBuilder setLayoutLabelResource(
+ @StringRes int layoutLabelResId) {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return this;
+ }
+ mLayoutLabelResId = layoutLabelResId;
+ return this;
+ }
+ /** The layout label string resource identifier. */
+ @StringRes
+ private int mLayoutLabelResId = 0;
+
+ /**
+ * Sets the non-localized layout label. This is used as the layout display name if the
+ * {@link #getLayoutLabelResource layoutLabelResource} is not set ({@code 0}).
+ *
+ * @param layoutLabelNonLocalized the non-localized layout label.
+ *
+ * @see #getLayoutDisplayName
+ */
+ @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+ @NonNull
+ public InputMethodSubtypeBuilder setLayoutLabelNonLocalized(
+ @NonNull CharSequence layoutLabelNonLocalized) {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return this;
+ }
+ Objects.requireNonNull(layoutLabelNonLocalized,
+ "layoutLabelNonLocalized cannot be null");
+ mLayoutLabelNonLocalized = layoutLabelNonLocalized;
+ return this;
+ }
+ /** The non-localized layout label. */
+ @NonNull
+ private CharSequence mLayoutLabelNonLocalized = "";
+
+ /**
* Sets the physical keyboard hint information, such as language and layout.
*
* The system can use the hint information to automatically configure the physical keyboard
@@ -350,6 +409,8 @@ public final class InputMethodSubtype implements Parcelable {
private InputMethodSubtype(InputMethodSubtypeBuilder builder) {
mSubtypeNameResId = builder.mSubtypeNameResId;
mSubtypeNameOverride = builder.mSubtypeNameOverride;
+ mLayoutLabelResId = builder.mLayoutLabelResId;
+ mLayoutLabelNonLocalized = builder.mLayoutLabelNonLocalized;
mPkLanguageTag = builder.mPkLanguageTag;
mPkLayoutType = builder.mPkLayoutType;
mSubtypeIconResId = builder.mSubtypeIconResId;
@@ -376,6 +437,9 @@ public final class InputMethodSubtype implements Parcelable {
mSubtypeNameResId = source.readInt();
CharSequence cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
mSubtypeNameOverride = cs != null ? cs : "";
+ mLayoutLabelResId = source.readInt();
+ cs = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+ mLayoutLabelNonLocalized = cs != null ? cs : "";
s = source.readString8();
mPkLanguageTag = s != null ? s : "";
s = source.readString8();
@@ -412,6 +476,24 @@ public final class InputMethodSubtype implements Parcelable {
}
/**
+ * Returns the layout label string resource identifier.
+ */
+ @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+ @StringRes
+ public int getLayoutLabelResource() {
+ return mLayoutLabelResId;
+ }
+
+ /**
+ * Returns the non-localized layout label.
+ */
+ @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+ @NonNull
+ public CharSequence getLayoutLabelNonLocalized() {
+ return mLayoutLabelNonLocalized;
+ }
+
+ /**
* Returns the physical keyboard BCP-47 language tag.
*
* @attr ref android.R.styleable#InputMethod_Subtype_physicalKeyboardHintLanguageTag
@@ -643,9 +725,47 @@ public final class InputMethodSubtype implements Parcelable {
try {
return String.format(subtypeNameString, replacementString);
} catch (IllegalFormatException e) {
- Slog.w(TAG, "Found illegal format in subtype name("+ subtypeName + "): " + e);
+ Slog.w(TAG, "Found illegal format in subtype name(" + subtypeName + "): " + e);
+ return "";
+ }
+ }
+
+ /**
+ * Returns the layout display name.
+ *
+ * <p>If {@code layoutLabelResource} is non-zero (specified through
+ * {@link InputMethodSubtypeBuilder#setLayoutLabelResource setLayoutLabelResource}), the
+ * text generated from that resource will be returned. The localized string resource of the
+ * label should be capitalized for inclusion in UI lists.
+ *
+ * <p>If {@code layoutLabelResource} is zero, the framework returns the non-localized
+ * layout label, if specified through
+ * {@link InputMethodSubtypeBuilder#setLayoutLabelNonLocalized setLayoutLabelNonLocalized}.
+ *
+ * @param context The context used for getting the
+ * {@link android.content.pm.PackageManager PackageManager}.
+ * @param imeAppInfo The {@link ApplicationInfo} of the input method.
+ * @return the layout display name.
+ */
+ @NonNull
+ @FlaggedApi(Flags.FLAG_IME_SWITCHER_REVAMP_API)
+ public CharSequence getLayoutDisplayName(@NonNull Context context,
+ @NonNull ApplicationInfo imeAppInfo) {
+ if (!Flags.imeSwitcherRevampApi()) {
+ return "";
+ }
+ Objects.requireNonNull(context, "context cannot be null");
+ Objects.requireNonNull(imeAppInfo, "imeAppInfo cannot be null");
+ if (mLayoutLabelResId == 0) {
+ return mLayoutLabelNonLocalized;
+ }
+
+ final CharSequence subtypeLayoutName = context.getPackageManager().getText(
+ imeAppInfo.packageName, mLayoutLabelResId, imeAppInfo);
+ if (TextUtils.isEmpty(subtypeLayoutName)) {
return "";
}
+ return subtypeLayoutName;
}
@Nullable
@@ -778,6 +898,8 @@ public final class InputMethodSubtype implements Parcelable {
public void writeToParcel(Parcel dest, int parcelableFlags) {
dest.writeInt(mSubtypeNameResId);
TextUtils.writeToParcel(mSubtypeNameOverride, dest, parcelableFlags);
+ dest.writeInt(mLayoutLabelResId);
+ TextUtils.writeToParcel(mLayoutLabelNonLocalized, dest, parcelableFlags);
dest.writeString8(mPkLanguageTag);
dest.writeString8(mPkLayoutType);
dest.writeInt(mSubtypeIconResId);
@@ -794,6 +916,7 @@ public final class InputMethodSubtype implements Parcelable {
void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride
+ + " mLayoutLabelNonLocalized=" + mLayoutLabelNonLocalized
+ " mPkLanguageTag=" + mPkLanguageTag
+ " mPkLayoutType=" + mPkLayoutType
+ " mSubtypeId=" + mSubtypeId
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 14505f527195..0f2dd10d7f47 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -169,8 +169,11 @@ public final class TransitionInfo implements Parcelable {
/** This change represents its start configuration for the duration of the animation. */
public static final int FLAG_CONFIG_AT_END = 1 << 22;
+ /** This change represents one of a Task Display Area. */
+ public static final int FLAG_IS_TASK_DISPLAY_AREA = 1 << 23;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 23;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 24;
/** The change belongs to a window that won't contain activities. */
public static final int FLAGS_IS_NON_APP_WINDOW =
@@ -205,6 +208,7 @@ public final class TransitionInfo implements Parcelable {
FLAG_MOVED_TO_TOP,
FLAG_SYNC,
FLAG_CONFIG_AT_END,
+ FLAG_IS_TASK_DISPLAY_AREA,
FLAG_FIRST_CUSTOM
}, flag = true)
public @interface ChangeFlags {}
@@ -553,6 +557,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_MOVED_TO_TOP) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("MOVE_TO_TOP");
}
+ if ((flags & FLAG_IS_TASK_DISPLAY_AREA) != 0) {
+ sb.append(sb.length() == 0 ? "" : "|").append("FLAG_IS_TASK_DISPLAY_AREA");
+ }
return sb.toString();
}
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index fd5de91c80ca..b2f125dd2821 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -16,13 +16,6 @@ flag {
}
flag {
- name: "bal_show_toasts"
- namespace: "responsible_apis"
- description: "Enable toasts to indicate (potential) BAL blocking."
- bug: "308059069"
-}
-
-flag {
name: "bal_show_toasts_blocked"
namespace: "responsible_apis"
description: "Enable toasts to indicate actual BAL blocking."
@@ -64,14 +57,6 @@ flag {
bug: "339720406"
}
-# replaced by bal_strict_mode_ro
-flag {
- name: "bal_strict_mode"
- namespace: "responsible_apis"
- description: "Strict mode flag"
- bug: "324089586"
-}
-
flag {
name: "bal_strict_mode_ro"
namespace: "responsible_apis"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 460df3103488..392c307de7ba 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -13,14 +13,6 @@ flag {
flag {
namespace: "window_surfaces"
- name: "explicit_refresh_rate_hints"
- description: "Performance related hints during transitions"
- is_fixed_read_only: true
- bug: "300019131"
-}
-
-flag {
- namespace: "window_surfaces"
name: "delete_capture_display"
description: "Delete uses of ScreenCapture#captureDisplay"
is_fixed_read_only: true
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
index de3edeb22a40..15736ed3f4e1 100644
--- a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -18,14 +18,13 @@ package com.android.internal.os;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import dalvik.annotation.optimization.CriticalNative;
-
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -255,8 +254,8 @@ public class KernelSingleUidTimeReader {
* the delta in the supplied array container.
*/
public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
- mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
+ long[] delta) {
+ mInjector.addDelta(uid, counter, timestampMs, delta);
}
@VisibleForTesting
@@ -274,15 +273,13 @@ public class KernelSingleUidTimeReader {
* The delta is also returned via the optional deltaOut parameter.
*/
public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
- return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
- deltaOut != null ? deltaOut.mNativeObject : 0);
+ long[] deltaOut) {
+ return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, deltaOut);
}
- @CriticalNative
private static native boolean addDeltaFromBpf(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
- long longArrayContainerNativePointer);
+ @Nullable long[] deltaOut);
/**
* Used for testing.
@@ -291,14 +288,14 @@ public class KernelSingleUidTimeReader {
*/
public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
long timestampMs, long[][] timeInFreqDataNanos,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ long[] deltaOut) {
return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
- deltaOut != null ? deltaOut.mNativeObject : 0);
+ deltaOut);
}
private static native boolean addDeltaForTest(int uid,
long longArrayMultiStateCounterNativePointer, long timestampMs,
- long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
+ long[][] timeInFreqDataNanos, long[] deltaOut);
}
@VisibleForTesting
diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
index 489721fbc10e..b3480ab92f46 100644
--- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
+++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java
@@ -30,9 +30,6 @@ import dalvik.annotation.optimization.FastNative;
import libcore.util.NativeAllocationRegistry;
-import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicReference;
-
/**
* Performs per-state counting of multi-element values over time. The class' behavior is illustrated
* by this example:
@@ -44,15 +41,14 @@ import java.util.concurrent.atomic.AtomicReference;
* counter.setState(1, 1000);
*
* // At 3000 ms, the tracked values are updated to {30, 300}
- * arrayContainer.setValues(new long[]{{30, 300}};
- * counter.updateValues(arrayContainer, 3000);
+ * counter.updateValues(arrayContainer, new long[]{{30, 300}, 3000);
*
* // The values are distributed between states 0 and 1 according to the time
* // spent in those respective states. In this specific case, 1000 and 2000 ms.
- * counter.getValues(arrayContainer, 0);
- * // arrayContainer now has values {10, 100}
- * counter.getValues(arrayContainer, 1);
- * // arrayContainer now has values {20, 200}
+ * counter.getCounts(array, 0);
+ * // array now has values {10, 100}
+ * counter.getCounts(array, 1);
+ * // array now has values {20, 200}
* </pre>
*
* The tracked values are expected to increase monotonically.
@@ -62,110 +58,7 @@ import java.util.concurrent.atomic.AtomicReference;
@RavenwoodKeepWholeClass
@RavenwoodRedirectionClass("LongArrayMultiStateCounter_host")
public final class LongArrayMultiStateCounter implements Parcelable {
-
- /**
- * Container for a native equivalent of a long[].
- */
- @RavenwoodKeepWholeClass
- @RavenwoodRedirectionClass("LongArrayContainer_host")
- public static class LongArrayContainer {
- private static NativeAllocationRegistry sRegistry;
-
- // Visible to other objects in this package so that it can be passed to @CriticalNative
- // methods.
- final long mNativeObject;
- private final int mLength;
-
- public LongArrayContainer(int length) {
- mLength = length;
- mNativeObject = native_init(length);
- registerNativeAllocation();
- }
-
- @RavenwoodReplace
- private void registerNativeAllocation() {
- if (sRegistry == null) {
- synchronized (LongArrayMultiStateCounter.class) {
- if (sRegistry == null) {
- sRegistry = NativeAllocationRegistry.createMalloced(
- LongArrayContainer.class.getClassLoader(), native_getReleaseFunc());
- }
- }
- }
- sRegistry.registerNativeAllocation(this, mNativeObject);
- }
-
- private void registerNativeAllocation$ravenwood() {
- // No-op under ravenwood
- }
-
- /**
- * Copies the supplied values into the underlying native array.
- */
- public void setValues(long[] array) {
- if (array.length != mLength) {
- throw new IllegalArgumentException(
- "Invalid array length: " + array.length + ", expected: " + mLength);
- }
- native_setValues(mNativeObject, array);
- }
-
- /**
- * Copies the underlying native array values to the supplied array.
- */
- public void getValues(long[] array) {
- if (array.length != mLength) {
- throw new IllegalArgumentException(
- "Invalid array length: " + array.length + ", expected: " + mLength);
- }
- native_getValues(mNativeObject, array);
- }
-
- /**
- * Combines contained values into a smaller array by aggregating them
- * according to an index map.
- */
- public boolean combineValues(long[] array, int[] indexMap) {
- if (indexMap.length != mLength) {
- throw new IllegalArgumentException(
- "Wrong index map size " + indexMap.length + ", expected " + mLength);
- }
- return native_combineValues(mNativeObject, array, indexMap);
- }
-
- @Override
- public String toString() {
- final long[] array = new long[mLength];
- getValues(array);
- return Arrays.toString(array);
- }
-
- @CriticalNative
- @RavenwoodRedirect
- private static native long native_init(int length);
-
- @CriticalNative
- @RavenwoodRedirect
- private static native long native_getReleaseFunc();
-
- @FastNative
- @RavenwoodRedirect
- private static native void native_setValues(long nativeObject, long[] array);
-
- @FastNative
- @RavenwoodRedirect
- private static native void native_getValues(long nativeObject, long[] array);
-
- @FastNative
- @RavenwoodRedirect
- private static native boolean native_combineValues(long nativeObject, long[] array,
- int[] indexMap);
- }
-
private static volatile NativeAllocationRegistry sRegistry;
- private static final AtomicReference<LongArrayContainer> sTmpArrayContainer =
- new AtomicReference<>();
-
private final int mStateCount;
private final int mLength;
@@ -257,41 +150,14 @@ public final class LongArrayMultiStateCounter implements Parcelable {
throw new IllegalArgumentException(
"Invalid array length: " + values.length + ", expected: " + mLength);
}
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
- }
- container.setValues(values);
- native_setValues(mNativeObject, state, container.mNativeObject);
- sTmpArrayContainer.set(container);
- }
-
- /**
- * Sets the new values. The delta between the previously set values and these values
- * is distributed among the state according to the time the object spent in those states
- * since the previous call to updateValues.
- */
- public void updateValues(long[] values, long timestampMs) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
- }
- container.setValues(values);
- updateValues(container, timestampMs);
- sTmpArrayContainer.set(container);
+ native_setValues(mNativeObject, state, values);
}
/**
* Adds the supplied values to the current accumulated values in the counter.
*/
public void incrementValues(long[] values, long timestampMs) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != values.length) {
- container = new LongArrayContainer(values.length);
- }
- container.setValues(values);
- native_incrementValues(mNativeObject, container.mNativeObject, timestampMs);
- sTmpArrayContainer.set(container);
+ native_incrementValues(mNativeObject, values, timestampMs);
}
/**
@@ -299,24 +165,23 @@ public final class LongArrayMultiStateCounter implements Parcelable {
* is distributed among the state according to the time the object spent in those states
* since the previous call to updateValues.
*/
- public void updateValues(LongArrayContainer longArrayContainer, long timestampMs) {
- if (longArrayContainer.mLength != mLength) {
+ public void updateValues(long[] values, long timestampMs) {
+ if (values.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + longArrayContainer.mLength + ", expected: "
- + mLength);
+ "Invalid array length: " + values.length + ", expected: " + mLength);
}
- native_updateValues(mNativeObject, longArrayContainer.mNativeObject, timestampMs);
+ native_updateValues(mNativeObject, values, timestampMs);
}
/**
* Adds the supplied values to the current accumulated values in the counter.
*/
- public void addCounts(LongArrayContainer counts) {
- if (counts.mLength != mLength) {
+ public void addCounts(long[] counts) {
+ if (counts.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + counts.mLength + ", expected: " + mLength);
+ "Invalid array length: " + counts.length + ", expected: " + mLength);
}
- native_addCounts(mNativeObject, counts.mNativeObject);
+ native_addCounts(mNativeObject, counts);
}
/**
@@ -330,29 +195,15 @@ public final class LongArrayMultiStateCounter implements Parcelable {
* Populates the array with the accumulated counts for the specified state.
*/
public void getCounts(long[] counts, int state) {
- LongArrayContainer container = sTmpArrayContainer.getAndSet(null);
- if (container == null || container.mLength != counts.length) {
- container = new LongArrayContainer(counts.length);
- }
- getCounts(container, state);
- container.getValues(counts);
- sTmpArrayContainer.set(container);
- }
-
- /**
- * Populates longArrayContainer with the accumulated counts for the specified state.
- */
- public void getCounts(LongArrayContainer longArrayContainer, int state) {
if (state < 0 || state >= mStateCount) {
throw new IllegalArgumentException(
"State: " + state + ", outside the range: [0-" + mStateCount + "]");
}
- if (longArrayContainer.mLength != mLength) {
+ if (counts.length != mLength) {
throw new IllegalArgumentException(
- "Invalid array length: " + longArrayContainer.mLength
- + ", expected: " + mLength);
+ "Invalid array length: " + counts.length + ", expected: " + mLength);
}
- native_getCounts(mNativeObject, longArrayContainer.mNativeObject, state);
+ native_getCounts(mNativeObject, counts, state);
}
@Override
@@ -370,18 +221,17 @@ public final class LongArrayMultiStateCounter implements Parcelable {
return 0;
}
- public static final Creator<LongArrayMultiStateCounter> CREATOR =
- new Creator<LongArrayMultiStateCounter>() {
- @Override
- public LongArrayMultiStateCounter createFromParcel(Parcel in) {
- return new LongArrayMultiStateCounter(in);
- }
+ public static final Creator<LongArrayMultiStateCounter> CREATOR = new Creator<>() {
+ @Override
+ public LongArrayMultiStateCounter createFromParcel(Parcel in) {
+ return new LongArrayMultiStateCounter(in);
+ }
- @Override
- public LongArrayMultiStateCounter[] newArray(int size) {
- return new LongArrayMultiStateCounter[size];
- }
- };
+ @Override
+ public LongArrayMultiStateCounter[] newArray(int size) {
+ return new LongArrayMultiStateCounter[size];
+ }
+ };
@CriticalNative
@@ -406,34 +256,31 @@ public final class LongArrayMultiStateCounter implements Parcelable {
private static native void native_copyStatesFrom(long nativeObjectTarget,
long nativeObjectSource);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_setValues(long nativeObject, int state,
- long longArrayContainerNativeObject);
+ private static native void native_setValues(long nativeObject, int state, long[] values);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_updateValues(long nativeObject,
- long longArrayContainerNativeObject, long timestampMs);
+ private static native void native_updateValues(long nativeObject, long[] values,
+ long timestampMs);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_incrementValues(long nativeObject,
- long longArrayContainerNativeObject, long timestampMs);
+ private static native void native_incrementValues(long nativeObject, long[] values,
+ long timestampMs);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_addCounts(long nativeObject,
- long longArrayContainerNativeObject);
+ private static native void native_addCounts(long nativeObject, long[] counts);
@CriticalNative
@RavenwoodRedirect
private static native void native_reset(long nativeObject);
- @CriticalNative
+ @FastNative
@RavenwoodRedirect
- private static native void native_getCounts(long nativeObject,
- long longArrayContainerNativeObject, int state);
+ private static native void native_getCounts(long nativeObject, long[] counts, int state);
@FastNative
@RavenwoodRedirect
diff --git a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
index 30b160ab161b..a69d2e4f4dca 100644
--- a/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
+++ b/core/java/com/android/internal/ravenwood/RavenwoodEnvironment.java
@@ -28,19 +28,9 @@ import android.ravenwood.annotation.RavenwoodReplace;
public final class RavenwoodEnvironment {
public static final String TAG = "RavenwoodEnvironment";
- private static final RavenwoodEnvironment sInstance;
- private static final Workaround sWorkaround;
+ private static RavenwoodEnvironment sInstance = new RavenwoodEnvironment();
- private RavenwoodEnvironment() {
- }
-
- static {
- sInstance = new RavenwoodEnvironment();
- sWorkaround = new Workaround();
- ensureRavenwoodInitialized();
- }
-
- public static RuntimeException notSupportedOnDevice() {
+ private static RuntimeException notSupportedOnDevice() {
return new UnsupportedOperationException("This method can only be used on Ravenwood");
}
@@ -52,15 +42,6 @@ public final class RavenwoodEnvironment {
}
/**
- * Initialize the ravenwood environment if it hasn't happened already, if running on Ravenwood.
- *
- * No-op if called on the device side.
- */
- @RavenwoodRedirect
- public static void ensureRavenwoodInitialized() {
- }
-
- /**
* USE IT SPARINGLY! Returns true if it's running on Ravenwood, hostside test environment.
*
* <p>Using this allows code to behave differently on a real device and on Ravenwood, but
@@ -91,38 +72,10 @@ public final class RavenwoodEnvironment {
}
/**
- * See {@link Workaround}. It's only usable on Ravenwood.
- */
- @RavenwoodReplace
- public static Workaround workaround() {
- throw notSupportedOnDevice();
- }
-
- private static Workaround workaround$ravenwood() {
- return sWorkaround;
- }
-
- /**
* @return the "ravenwood-runtime" directory.
*/
@RavenwoodRedirect
public String getRavenwoodRuntimePath() {
throw notSupportedOnDevice();
}
-
- /**
- * A set of APIs used to work around missing features on Ravenwood. Ideally, this class should
- * be empty, and all its APIs should be able to be implemented properly.
- */
- public static class Workaround {
- Workaround() {
- }
-
- /**
- * @return whether the app's target SDK level is at least Q.
- */
- public boolean isTargetSdkAtLeastQ() {
- return true;
- }
- }
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index df87a69f02ce..56292c3d0fb2 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -2403,6 +2403,11 @@ SurfaceComposerClient::Transaction* android_view_SurfaceTransaction_getNativeSur
}
}
+static void nativeEnableDebugLogCallPoints(JNIEnv* env, jclass clazz, jlong transactionObj) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ transaction->enableDebugLogCallPoints();
+}
+
static const JNINativeMethod sSurfaceControlMethods[] = {
// clang-format off
{"nativeCreate", "(Landroid/view/SurfaceSession;Ljava/lang/String;IIIIJLandroid/os/Parcel;)J",
@@ -2649,6 +2654,7 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
{"nativeNotifyShutdown", "()V",
(void*)nativeNotifyShutdown },
{"nativeSetLuts", "(JJ[F[I[I[I[I)V", (void*)nativeSetLuts },
+ {"nativeEnableDebugLogCallPoints", "(J)V", (void*)nativeEnableDebugLogCallPoints },
// clang-format on
};
diff --git a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
index bea5ffe31da0..a5b5057ecbac 100644
--- a/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
+++ b/core/jni/com_android_internal_os_KernelSingleUidTimeReader.cpp
@@ -49,27 +49,26 @@ static jlongArray getUidCpuFreqTimeMs(JNIEnv *env, jclass, jint uid) {
* to the supplied multi-state counter in accordance with the counter's state.
*/
static jboolean addCpuTimeInFreqDelta(
- jint uid, jlong counterNativePtr, jlong timestampMs,
+ JNIEnv *env, jint uid, jlong counterNativePtr, jlong timestampMs,
std::optional<std::vector<std::vector<uint64_t>>> timeInFreqDataNanos,
- jlong deltaOutContainerNativePtr) {
+ jlongArray deltaOut) {
if (!timeInFreqDataNanos) {
return false;
}
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
+ auto counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(counterNativePtr);
size_t s = 0;
for (const auto &cluster : *timeInFreqDataNanos) s += cluster.size();
- std::vector<uint64_t> flattened;
- flattened.reserve(s);
- auto offset = flattened.begin();
+ battery::Uint64ArrayRW flattened(s);
+ uint64_t *out = flattened.dataRW();
+ auto offset = out;
for (const auto &cluster : *timeInFreqDataNanos) {
- flattened.insert(offset, cluster.begin(), cluster.end());
+ memcpy(offset, cluster.data(), cluster.size() * sizeof(uint64_t));
offset += cluster.size();
}
for (size_t i = 0; i < s; ++i) {
- flattened[i] /= NSEC_PER_MSEC;
+ out[i] /= NSEC_PER_MSEC;
}
if (s != counter->getCount(0).size()) { // Counter has at least one state
ALOGE("Mismatch between eBPF data size (%d) and the counter size (%d)", (int)s,
@@ -77,29 +76,32 @@ static jboolean addCpuTimeInFreqDelta(
return false;
}
- const std::vector<uint64_t> &delta = counter->updateValue(flattened, timestampMs);
- if (deltaOutContainerNativePtr) {
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(deltaOutContainerNativePtr);
- *vector = delta;
+ const battery::Uint64Array &delta = counter->updateValue(flattened, timestampMs);
+ if (deltaOut) {
+ ScopedLongArrayRW scopedArray(env, deltaOut);
+ uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
+ if (delta.data() != nullptr) {
+ memcpy(array, delta.data(), s * sizeof(uint64_t));
+ } else {
+ memset(array, 0, s * sizeof(uint64_t));
+ }
}
return true;
}
-static jboolean addDeltaFromBpf(jint uid, jlong counterNativePtr, jlong timestampMs,
- jlong deltaOutContainerNativePtr) {
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
- android::bpf::getUidCpuFreqTimes(uid), deltaOutContainerNativePtr);
+static jboolean addDeltaFromBpf(JNIEnv *env, jlong self, jint uid, jlong counterNativePtr,
+ jlong timestampMs, jlongArray deltaOut) {
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ android::bpf::getUidCpuFreqTimes(uid), deltaOut);
}
static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNativePtr,
jlong timestampMs, jobjectArray timeInFreqDataNanos,
- jlong deltaOutContainerNativePtr) {
+ jlongArray deltaOut) {
if (!timeInFreqDataNanos) {
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs,
- std::optional<std::vector<std::vector<uint64_t>>>(),
- deltaOutContainerNativePtr);
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ std::optional<std::vector<std::vector<uint64_t>>>(), deltaOut);
}
std::vector<std::vector<uint64_t>> timeInFreqData;
@@ -113,18 +115,16 @@ static jboolean addDeltaForTest(JNIEnv *env, jclass, jint uid, jlong counterNati
}
timeInFreqData.push_back(cluster);
}
- return addCpuTimeInFreqDelta(uid, counterNativePtr, timestampMs, std::optional(timeInFreqData),
- deltaOutContainerNativePtr);
+ return addCpuTimeInFreqDelta(env, uid, counterNativePtr, timestampMs,
+ std::optional(timeInFreqData), deltaOut);
}
static const JNINativeMethod g_single_methods[] = {
{"readBpfData", "(I)[J", (void *)getUidCpuFreqTimeMs},
-
- // @CriticalNative
- {"addDeltaFromBpf", "(IJJJ)Z", (void *)addDeltaFromBpf},
+ {"addDeltaFromBpf", "(IJJ[J)Z", (void *)addDeltaFromBpf},
// Used for testing
- {"addDeltaForTest", "(IJJ[[JJ)Z", (void *)addDeltaForTest},
+ {"addDeltaForTest", "(IJJ[[J[J)Z", (void *)addDeltaForTest},
};
int register_com_android_internal_os_KernelSingleUidTimeReader(JNIEnv *env) {
diff --git a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
index b3c41dfe81a1..7ffe0ed7c6cd 100644
--- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp
@@ -26,16 +26,40 @@
#include "core_jni_helpers.h"
namespace android {
+namespace battery {
+
+/**
+ * Implementation of Uint64Array that wraps a Java long[]. Since it uses the "critical"
+ * version of JNI array access (halting GC), any usage of this class must be extra quick.
+ */
+class JavaUint64Array : public Uint64Array {
+ JNIEnv *mEnv;
+ jlongArray mJavaArray;
+ uint64_t *mData;
+
+public:
+ JavaUint64Array(JNIEnv *env, jlongArray values) : Uint64Array(env->GetArrayLength(values)) {
+ mEnv = env;
+ mJavaArray = values;
+ mData = reinterpret_cast<uint64_t *>(mEnv->GetPrimitiveArrayCritical(mJavaArray, nullptr));
+ }
+
+ ~JavaUint64Array() override {
+ mEnv->ReleasePrimitiveArrayCritical(mJavaArray, mData, 0);
+ }
+
+ const uint64_t *data() const override {
+ return mData;
+ }
+};
static jlong native_init(jint stateCount, jint arrayLength) {
- battery::LongArrayMultiStateCounter *counter =
- new battery::LongArrayMultiStateCounter(stateCount, std::vector<uint64_t>(arrayLength));
+ auto *counter = new LongArrayMultiStateCounter(stateCount, Uint64Array(arrayLength));
return reinterpret_cast<jlong>(counter);
}
static void native_dispose(void *nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
delete counter;
}
@@ -44,80 +68,63 @@ static jlong native_getReleaseFunc() {
}
static void native_setEnabled(jlong nativePtr, jboolean enabled, jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->setEnabled(enabled, timestamp);
}
static void native_setState(jlong nativePtr, jint state, jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->setState(state, timestamp);
}
static void native_copyStatesFrom(jlong nativePtrTarget, jlong nativePtrSource) {
- battery::LongArrayMultiStateCounter *counterTarget =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
- battery::LongArrayMultiStateCounter *counterSource =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
+ auto *counterTarget = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrTarget);
+ auto *counterSource = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtrSource);
counterTarget->copyStatesFrom(*counterSource);
}
-static void native_setValues(jlong nativePtr, jint state, jlong longArrayContainerNativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->setValue(state, *vector);
+static void native_setValues(JNIEnv *env, jclass, jlong nativePtr, jint state, jlongArray values) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->setValue(state, JavaUint64Array(env, values));
}
-static void native_updateValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_updateValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->updateValue(*vector, timestamp);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->updateValue(JavaUint64Array(env, values), timestamp);
}
-static void native_incrementValues(jlong nativePtr, jlong longArrayContainerNativePtr,
+static void native_incrementValues(JNIEnv *env, jclass, jlong nativePtr, jlongArray values,
jlong timestamp) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- counter->incrementValue(*vector, timestamp);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->incrementValue(JavaUint64Array(env, values), timestamp);
}
-static void native_addCounts(jlong nativePtr, jlong longArrayContainerNativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
- counter->addValue(*vector);
+static void native_addCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ counter->addValue(JavaUint64Array(env, values));
}
static void native_reset(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
counter->reset();
}
-static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, jint state) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
- std::vector<uint64_t> *vector =
- reinterpret_cast<std::vector<uint64_t> *>(longArrayContainerNativePtr);
-
- *vector = counter->getCount(state);
+static void native_getCounts(JNIEnv *env, jclass, jlong nativePtr, jlongArray values, jint state) {
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
+ ScopedLongArrayRW scopedArray(env, values);
+ auto *data = counter->getCount(state).data();
+ auto size = env->GetArrayLength(values);
+ auto *outData = scopedArray.get();
+ if (data == nullptr) {
+ memset(outData, 0, size * sizeof(uint64_t));
+ } else {
+ memcpy(outData, data, size * sizeof(uint64_t));
+ }
}
static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
return env->NewStringUTF(counter->toString().c_str());
}
@@ -137,20 +144,26 @@ static void throwWriteRE(JNIEnv *env, binder_status_t status) {
static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel,
jint flags) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel));
uint16_t stateCount = counter->getStateCount();
THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), stateCount));
// LongArrayMultiStateCounter has at least state 0
- const std::vector<uint64_t> &anyState = counter->getCount(0);
+ const Uint64Array &anyState = counter->getCount(0);
THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeInt32(parcel.get(), anyState.size()));
for (battery::state_t state = 0; state < stateCount; state++) {
- THROW_AND_RETURN_ON_WRITE_ERROR(
- ndk::AParcel_writeVector(parcel.get(), counter->getCount(state)));
+ const Uint64Array &value = counter->getCount(state);
+ if (value.data() == nullptr) {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), false));
+ } else {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeBool(parcel.get(), true));
+ for (size_t i = 0; i < anyState.size(); i++) {
+ THROW_AND_RETURN_ON_WRITE_ERROR(AParcel_writeUint64(parcel.get(), value.data()[i]));
+ }
+ }
}
}
@@ -183,40 +196,37 @@ static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) {
int32_t arrayLength;
THROW_AND_RETURN_ON_READ_ERROR(AParcel_readInt32(parcel.get(), &arrayLength));
- auto counter = std::make_unique<battery::LongArrayMultiStateCounter>(stateCount,
- std::vector<uint64_t>(
- arrayLength));
-
- std::vector<uint64_t> value;
- value.reserve(arrayLength);
-
+ auto counter =
+ std::make_unique<LongArrayMultiStateCounter>(stateCount, Uint64Array(arrayLength));
+ Uint64ArrayRW array(arrayLength);
for (battery::state_t state = 0; state < stateCount; state++) {
- THROW_AND_RETURN_ON_READ_ERROR(ndk::AParcel_readVector(parcel.get(), &value));
- counter->setValue(state, value);
+ bool hasValues;
+ THROW_AND_RETURN_ON_READ_ERROR(AParcel_readBool(parcel.get(), &hasValues));
+ if (hasValues) {
+ for (int i = 0; i < arrayLength; i++) {
+ THROW_AND_RETURN_ON_READ_ERROR(
+ AParcel_readUint64(parcel.get(), &(array.dataRW()[i])));
+ }
+ counter->setValue(state, array);
+ }
}
return reinterpret_cast<jlong>(counter.release());
}
static jint native_getStateCount(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
return counter->getStateCount();
}
static jint native_getArrayLength(jlong nativePtr) {
- battery::LongArrayMultiStateCounter *counter =
- reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr);
+ auto *counter = reinterpret_cast<LongArrayMultiStateCounter *>(nativePtr);
// LongArrayMultiStateCounter has at least state 0
- const std::vector<uint64_t> &anyState = counter->getCount(0);
+ const Uint64Array &anyState = counter->getCount(0);
return anyState.size();
}
-static jlong native_init_LongArrayContainer(jint length) {
- return reinterpret_cast<jlong>(new std::vector<uint64_t>(length));
-}
-
static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
// @CriticalNative
{"native_init", "(II)J", (void *)native_init},
@@ -228,18 +238,18 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
{"native_setState", "(JIJ)V", (void *)native_setState},
// @CriticalNative
{"native_copyStatesFrom", "(JJ)V", (void *)native_copyStatesFrom},
- // @CriticalNative
- {"native_setValues", "(JIJ)V", (void *)native_setValues},
- // @CriticalNative
- {"native_updateValues", "(JJJ)V", (void *)native_updateValues},
- // @CriticalNative
- {"native_incrementValues", "(JJJ)V", (void *)native_incrementValues},
- // @CriticalNative
- {"native_addCounts", "(JJ)V", (void *)native_addCounts},
+ // @FastNative
+ {"native_setValues", "(JI[J)V", (void *)native_setValues},
+ // @FastNative
+ {"native_updateValues", "(J[JJ)V", (void *)native_updateValues},
+ // @FastNative
+ {"native_incrementValues", "(J[JJ)V", (void *)native_incrementValues},
+ // @FastNative
+ {"native_addCounts", "(J[J)V", (void *)native_addCounts},
// @CriticalNative
{"native_reset", "(J)V", (void *)native_reset},
- // @CriticalNative
- {"native_getCounts", "(JJI)V", (void *)native_getCounts},
+ // @FastNative
+ {"native_getCounts", "(J[JI)V", (void *)native_getCounts},
// @FastNative
{"native_toString", "(J)Ljava/lang/String;", (void *)native_toString},
// @FastNative
@@ -252,91 +262,12 @@ static const JNINativeMethod g_LongArrayMultiStateCounter_methods[] = {
{"native_getArrayLength", "(J)I", (void *)native_getArrayLength},
};
-/////////////////////// LongArrayMultiStateCounter.LongArrayContainer ////////////////////////
-
-static void native_dispose_LongArrayContainer(jlong nativePtr) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- delete vector;
-}
-
-static jlong native_getReleaseFunc_LongArrayContainer() {
- return reinterpret_cast<jlong>(native_dispose_LongArrayContainer);
-}
-
-static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRO scopedArray(env, jarray);
- const uint64_t *array = reinterpret_cast<const uint64_t *>(scopedArray.get());
- uint8_t size = scopedArray.size();
-
- // Boundary checks are performed in the Java layer
- std::copy(array, array + size, vector->data());
-}
-
-static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRW scopedArray(env, jarray);
-
- // Boundary checks are performed in the Java layer
- std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get());
-}
-
-static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr,
- jlongArray jarray, jintArray jindexMap) {
- std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr);
- ScopedLongArrayRW scopedArray(env, jarray);
- ScopedIntArrayRO scopedIndexMap(env, jindexMap);
-
- const uint64_t *data = vector->data();
- uint64_t *array = reinterpret_cast<uint64_t *>(scopedArray.get());
- const uint8_t size = scopedArray.size();
-
- for (int i = 0; i < size; i++) {
- array[i] = 0;
- }
-
- bool nonZero = false;
- for (size_t i = 0; i < vector->size(); i++) {
- jint index = scopedIndexMap[i];
- if (index < 0 || index >= size) {
- jniThrowExceptionFmt(env, "java/lang/IndexOutOfBoundsException",
- "Index %d is out of bounds: [0, %d]", index, size - 1);
- return false;
- }
-
- if (data[i] != 0L) {
- array[index] += data[i];
- nonZero = true;
- }
- }
-
- return nonZero;
-}
-
-static const JNINativeMethod g_LongArrayContainer_methods[] = {
- // @CriticalNative
- {"native_init", "(I)J", (void *)native_init_LongArrayContainer},
- // @CriticalNative
- {"native_getReleaseFunc", "()J", (void *)native_getReleaseFunc_LongArrayContainer},
- // @FastNative
- {"native_setValues", "(J[J)V", (void *)native_setValues_LongArrayContainer},
- // @FastNative
- {"native_getValues", "(J[J)V", (void *)native_getValues_LongArrayContainer},
- // @FastNative
- {"native_combineValues", "(J[J[I)Z", (void *)native_combineValues_LongArrayContainer},
-};
+} // namespace battery
int register_com_android_internal_os_LongArrayMultiStateCounter(JNIEnv *env) {
// 0 represents success, thus "|" and not "&"
return RegisterMethodsOrDie(env, "com/android/internal/os/LongArrayMultiStateCounter",
- g_LongArrayMultiStateCounter_methods,
- NELEM(g_LongArrayMultiStateCounter_methods)) |
- RegisterMethodsOrDie(env,
- "com/android/internal/os/LongArrayMultiStateCounter"
- "$LongArrayContainer",
- g_LongArrayContainer_methods, NELEM(g_LongArrayContainer_methods));
+ battery::g_LongArrayMultiStateCounter_methods,
+ NELEM(battery::g_LongArrayMultiStateCounter_methods));
}
-
} // namespace android
diff --git a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
index 56d3fbba9458..b3bfd0bc5e09 100644
--- a/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
+++ b/core/jni/com_android_internal_os_LongMultiStateCounter.cpp
@@ -28,7 +28,7 @@ namespace android {
namespace battery {
-typedef battery::MultiStateCounter<int64_t> LongMultiStateCounter;
+typedef battery::MultiStateCounter<int64_t, int64_t> LongMultiStateCounter;
template <>
bool LongMultiStateCounter::delta(const int64_t &previousValue, const int64_t &newValue,
@@ -47,12 +47,6 @@ void LongMultiStateCounter::add(int64_t *value1, const int64_t &value2, const ui
*value1 += value2;
}
}
-
-template <>
-std::string LongMultiStateCounter::valueToString(const int64_t &v) const {
- return std::to_string(v);
-}
-
} // namespace battery
static inline battery::LongMultiStateCounter *asLongMultiStateCounter(const jlong nativePtr) {
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 654d83c827c9..407790c89202 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -465,6 +465,7 @@ message WindowStateProto {
repeated .android.graphics.RectProto unrestricted_keep_clear_areas = 46;
repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
optional int32 requested_visible_types = 48;
+ optional .android.graphics.RectProto dim_bounds = 49;
}
message IdentifierProto {
diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto
index 97350ef90eec..fb907196bfc7 100644
--- a/core/proto/android/service/appwidget.proto
+++ b/core/proto/android/service/appwidget.proto
@@ -20,6 +20,8 @@ package android.service.appwidget;
option java_multiple_files = true;
option java_outer_classname = "AppWidgetServiceProto";
+import "frameworks/base/core/proto/android/widget/remoteviews.proto";
+
// represents the object holding the dump info of the app widget service
message AppWidgetServiceDumpProto {
repeated WidgetProto widgets = 1; // the array of bound widgets
@@ -38,3 +40,14 @@ message WidgetProto {
optional int32 maxHeight = 9;
optional bool restoreCompleted = 10;
}
+
+// represents a set of widget previews for a particular provider
+message GeneratedPreviewsProto {
+ repeated Preview previews = 1;
+
+ // represents a particular RemoteViews preview, which may be set for multiple categories
+ message Preview {
+ repeated int32 widget_categories = 1;
+ optional android.widget.RemoteViewsProto views = 2;
+ }
+} \ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_lock_bugreport.xml b/core/res/res/drawable-watch/ic_lock_bugreport.xml
index b664fe4f9c4e..35834be7b541 100644
--- a/core/res/res/drawable-watch/ic_lock_bugreport.xml
+++ b/core/res/res/drawable-watch/ic_lock_bugreport.xml
@@ -13,19 +13,6 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24.0dp"
- android:height="24.0dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M20,10V8h-2.81c-0.45,-0.78 -1.07,-1.46 -1.82,-1.96L17,4.41L15.59,3l-2.17,2.17c-0.03,-0.01 -0.05,-0.01 -0.08,-0.01c-0.16,-0.04 -0.32,-0.06 -0.49,-0.09c-0.06,-0.01 -0.11,-0.02 -0.17,-0.03C12.46,5.02 12.23,5 12,5h0c-0.49,0 -0.97,0.07 -1.42,0.18l0.02,-0.01L8.41,3L7,4.41l1.62,1.63l0.01,0C7.88,6.54 7.26,7.22 6.81,8H4v2h2.09C6.03,10.33 6,10.66 6,11v1H4v2h2v1c0,0.34 0.04,0.67 0.09,1H4v2h2.81c1.04,1.79 2.97,3 5.19,3h0c2.22,0 4.15,-1.21 5.19,-3H20v-2h-2.09l0,0c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1l0,0H20zM16,15c0,2.21 -1.79,4 -4,4c-2.21,0 -4,-1.79 -4,-4v-4c0,-2.21 1.79,-4 4,-4h0c2.21,0 4,1.79 4,4V15z"/>
- <path
- android:fillColor="@android:color/white"
- android:pathData="M10,14h4v2h-4z"/>
- <path
- android:fillColor="@android:color/white"
- android:pathData="M10,10h4v2h-4z"/>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480.01,848.13Q412.37,848.13 354.6,814.34Q296.83,780.54 263.87,721.91L193.78,721.91Q175.97,721.91 163.92,709.86Q151.87,697.81 151.87,680Q151.87,662.19 163.92,650.14Q175.97,638.09 193.78,638.09L235.63,638.09Q232.81,619.12 232.34,600.16Q231.87,581.2 231.87,561.91L193.78,561.91Q175.97,561.91 163.92,549.86Q151.87,537.81 151.87,520Q151.87,502.19 163.92,490.14Q175.97,478.09 193.78,478.09L231.87,478.09Q231.87,458.8 232.34,439.84Q232.81,420.88 235.63,401.91L193.78,401.91Q175.97,401.91 163.92,389.86Q151.87,377.81 151.87,360Q151.87,342.19 163.92,330.14Q175.97,318.09 193.78,318.09L265.07,318.09Q278.11,294.13 295.97,273.77Q313.83,253.41 337.07,237.93L301.26,201.37Q289.54,189.65 289.66,172.32Q289.78,154.98 302.26,142.5Q313.98,130.78 331.7,130.78Q349.41,130.78 361.13,142.5L417.7,199.07Q447.13,188.63 477.92,188.39Q508.72,188.15 538.15,198.35L597.2,140.3Q608.79,128.59 626.19,128.59Q643.59,128.59 656.07,141.07Q667.78,152.78 667.78,170.5Q667.78,188.22 656.07,199.93L619.98,236.02Q644.41,251.98 663.51,273.03Q682.61,294.09 696.38,320L767.17,320Q784.41,320 796.27,331.86Q808.13,343.72 808.13,360.96Q808.13,378.2 796.27,390.05Q784.41,401.91 767.17,401.91L724.37,401.91Q727.19,420.88 727.66,439.84Q728.13,458.8 728.13,478.09L766.22,478.09Q784.03,478.09 796.08,490.14Q808.13,502.19 808.13,520Q808.13,537.81 796.08,549.86Q784.03,561.91 766.22,561.91L728.13,561.91Q728.13,581.2 727.51,600.12Q726.89,619.04 724.13,638.09L766.22,638.09Q784.03,638.09 796.08,650.14Q808.13,662.19 808.13,680Q808.13,697.81 796.08,709.86Q784.03,721.91 766.22,721.91L696.13,721.91Q663.17,780.54 605.42,814.34Q547.66,848.13 480.01,848.13ZM441.91,641.91L518.09,641.91Q535.9,641.91 547.95,629.86Q560,617.81 560,600Q560,582.19 547.95,570.14Q535.9,558.09 518.09,558.09L441.91,558.09Q424.1,558.09 412.05,570.14Q400,582.19 400,600Q400,617.81 412.05,629.86Q424.1,641.91 441.91,641.91ZM441.91,481.91L518.09,481.91Q535.9,481.91 547.95,469.86Q560,457.81 560,440Q560,422.19 547.95,410.14Q535.9,398.09 518.09,398.09L441.91,398.09Q424.1,398.09 412.05,410.14Q400,422.19 400,440Q400,457.81 412.05,469.86Q424.1,481.91 441.91,481.91Z"/>
</vector>
diff --git a/core/res/res/drawable-watch/ic_lock_power_off.xml b/core/res/res/drawable-watch/ic_lock_power_off.xml
index b437a4b70cca..c42d7d2e0293 100644
--- a/core/res/res/drawable-watch/ic_lock_power_off.xml
+++ b/core/res/res/drawable-watch/ic_lock_power_off.xml
@@ -14,13 +14,6 @@
~ limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M11,2h2v10h-2zM18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89 -2.73,2.73 -7.17,2.73 -9.89,0.01 -2.73,-2.73 -2.74,-7.18 -0.01,-9.91l-1.41,-1.4c-3.51,3.51 -3.51,9.21 0.01,12.73 3.51,3.51 9.21,3.51 12.72,-0.01 3.51,-3.51 3.51,-9.2 0,-12.72z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M480,888.13Q395.09,888.13 320.65,856.03Q246.22,823.93 191.14,768.86Q136.07,713.78 103.97,639.35Q71.87,564.91 71.87,480Q71.87,406.52 96.01,340.78Q120.15,275.04 162.91,222.33Q175.11,206.65 192.52,207.41Q209.93,208.17 222.61,219.37Q235.28,230.57 239.26,248.84Q243.24,267.11 227.8,287.22Q197.48,327.26 180.17,376.09Q162.87,424.91 162.87,480Q162.87,613.04 254.91,705.09Q346.96,797.13 480,797.13Q613.04,797.13 705.09,705.09Q797.13,613.04 797.13,480Q797.13,424.91 779.83,376.09Q762.52,327.26 732.2,287.22Q716.76,267.11 720.74,248.84Q724.72,230.57 737.39,219.37Q750.07,208.17 767.48,207.41Q784.89,206.65 797.09,222.33Q839.85,275.04 863.99,340.78Q888.13,406.52 888.13,480Q888.13,564.91 856.03,639.35Q823.93,713.78 768.86,768.86Q713.78,823.93 639.35,856.03Q564.91,888.13 480,888.13ZM480,525.5Q460.85,525.5 447.67,512.33Q434.5,499.15 434.5,480L434.5,117.37Q434.5,98.22 447.67,85.04Q460.85,71.87 480,71.87Q499.15,71.87 512.33,85.04Q525.5,98.22 525.5,117.37L525.5,480Q525.5,499.15 512.33,512.33Q499.15,525.5 480,525.5Z"/>
+</vector> \ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_restart.xml b/core/res/res/drawable-watch/ic_restart.xml
index 52933aae8fe0..ddcfd259d7b3 100644
--- a/core/res/res/drawable-watch/ic_restart.xml
+++ b/core/res/res/drawable-watch/ic_restart.xml
@@ -14,13 +14,6 @@
~ limitations under the License.
-->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24"
- android:viewportHeight="24"
- android:tint="@android:color/white">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02c-2.83,-0.48 -5,-2.94 -5,-5.91zM20,13c0,-4.42 -3.58,-8 -8,-8 -0.06,0 -0.12,0.01 -0.18,0.01l1.09,-1.09L11.5,2.5 8,6l3.5,3.5 1.41,-1.41 -1.08,-1.08c0.06,0 0.12,-0.01 0.17,-0.01 3.31,0 6,2.69 6,6 0,2.97 -2.17,5.43 -5,5.91v2.02c3.95,-0.49 7,-3.85 7,-7.93z"/>
-</vector>
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M385.83,834.46Q282.35,803.54 217.11,717.61Q151.87,631.67 151.87,520.48Q151.87,464.91 169.91,414.25Q187.96,363.59 220.8,320.83Q232.76,305.72 252.11,304.98Q271.46,304.24 286.85,319.63Q298.57,331.35 299.3,348.66Q300.04,365.98 288.8,381.41Q266.72,411.22 254.79,446.54Q242.87,481.87 242.87,520.48Q242.87,599.33 288.46,661.27Q334.04,723.22 406.41,746.7Q421.09,751.65 430.54,764.09Q440,776.52 440,790.96Q440,814.3 423.73,827.6Q407.46,840.89 385.83,834.46ZM574.17,834.46Q552.54,840.89 536.27,827.22Q520,813.54 520,790.2Q520,776.76 529.46,764.21Q538.91,751.65 553.59,746.7Q625.72,722.22 671.42,660.65Q717.13,599.09 717.13,520.48Q717.13,423.11 649.64,354.3Q582.15,285.5 485.02,283.59L481.07,283.59L497.3,299.83Q509.02,311.54 509.02,329.14Q509.02,346.74 497.3,358.46Q485.59,370.17 467.99,370.17Q450.39,370.17 438.67,358.46L348.46,268.24Q341.74,261.52 338.76,253.45Q335.78,245.37 335.78,236.41Q335.78,227.46 338.76,219.38Q341.74,211.3 348.46,204.59L438.67,114.13Q450.39,102.41 467.99,102.41Q485.59,102.41 497.3,114.13Q509.02,125.85 509.02,143.45Q509.02,161.04 497.3,172.76L477.72,192.35L481.91,192.35Q618.54,192.35 713.34,287.98Q808.13,383.61 808.13,520.48Q808.13,630.91 742.89,717.11Q677.65,803.3 574.17,834.46Z"/>
+</vector> \ No newline at end of file
diff --git a/core/res/res/drawable-watch/ic_settings.xml b/core/res/res/drawable-watch/ic_settings.xml
new file mode 100644
index 000000000000..cef10e9eed71
--- /dev/null
+++ b/core/res/res/drawable-watch/ic_settings.xml
@@ -0,0 +1,19 @@
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="960" android:viewportHeight="960" android:tint="?attr/colorControlNormal">
+<path android:fillColor="@android:color/white" android:pathData="M428.46,888.13Q400.26,888.13 379.92,869.53Q359.59,850.93 355.59,823.74L346.59,757.5Q335.5,753.22 325.55,747.17Q315.61,741.13 306.04,734.33L244.28,760.33Q218.33,771.57 192.13,762.45Q165.93,753.33 151.46,729.13L99.91,638.76Q85.43,614.8 91.55,587.73Q97.67,560.65 119.63,543.17L172.39,503.17Q171.63,497.13 171.63,491.59Q171.63,486.04 171.63,480Q171.63,473.96 171.63,468.41Q171.63,462.87 172.39,456.83L119.63,417.07Q97.43,399.59 91.43,372.51Q85.43,345.43 99.91,321.24L151.46,231.11Q165.93,207.15 192.01,197.91Q218.09,188.67 244.04,199.91L306.52,225.91Q316.09,219.11 326.17,213.18Q336.26,207.26 346.59,202.98L355.59,136.5Q359.59,109.07 379.92,90.47Q400.26,71.87 428.46,71.87L531.54,71.87Q559.74,71.87 580.08,90.47Q600.41,109.07 604.41,136.5L613.41,202.98Q624.5,207.26 634.45,213.18Q644.39,219.11 653.96,225.91L715.72,199.91Q741.67,188.67 767.87,197.91Q794.07,207.15 808.54,231.11L860.09,321.24Q874.57,345.43 868.57,372.51Q862.57,399.59 840.37,417.07L787.37,456.83Q788.13,462.87 788.13,468.41Q788.13,473.96 788.13,480Q788.13,486.04 788.01,491.59Q787.89,497.13 786.37,503.17L839.37,542.93Q861.57,560.41 867.57,587.49Q873.57,614.57 859.09,638.76L806.54,729.13Q792.07,753.09 765.99,762.33Q739.91,771.57 713.96,760.33L653.48,734.33Q643.91,741.13 633.83,747.17Q623.74,753.22 613.41,757.5L604.41,823.74Q600.41,850.93 580.08,869.53Q559.74,888.13 531.54,888.13L428.46,888.13ZM481.28,620Q539.28,620 580.28,579Q621.28,538 621.28,480Q621.28,422 580.28,381Q539.28,340 481.28,340Q422.52,340 381.9,381Q341.28,422 341.28,480Q341.28,538 381.9,579Q422.52,620 481.28,620Z"/>
+</vector> \ No newline at end of file
diff --git a/core/res/res/layout/input_method_switch_item_new.xml b/core/res/res/layout/input_method_switch_item_new.xml
index f8710cc45358..7b241aff3fb1 100644
--- a/core/res/res/layout/input_method_switch_item_new.xml
+++ b/core/res/res/layout/input_method_switch_item_new.xml
@@ -18,18 +18,20 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_item"
android:layout_width="match_parent"
- android:layout_height="72dp"
+ android:layout_height="wrap_content"
+ android:minHeight="72dp"
android:background="@drawable/input_method_switch_item_background"
android:gravity="center_vertical"
android:orientation="horizontal"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="8dp"
android:paddingStart="20dp"
- android:paddingEnd="24dp">
+ android:paddingEnd="24dp"
+ android:paddingVertical="8dp">
<LinearLayout
android:layout_width="0dp"
- android:layout_height="match_parent"
+ android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|center_vertical"
android:orientation="vertical">
@@ -39,11 +41,26 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
android:singleLine="true"
android:fontFamily="google-sans-text"
android:textColor="@color/input_method_switch_on_item"
android:textAppearance="?attr/textAppearanceListItem"/>
+ <TextView
+ android:id="@+id/text2"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4dp"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="1"
+ android:singleLine="true"
+ android:fontFamily="google-sans-text"
+ android:textColor="?attr/materialColorOnSurfaceVariant"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ android:textAllCaps="true"
+ android:visibility="gone"/>
+
</LinearLayout>
<ImageView
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 092d2a72580a..e6dedce8feaf 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4418,6 +4418,10 @@
<declare-styleable name="InputMethod_Subtype">
<!-- The name of the subtype. -->
<attr name="label" />
+ <!-- The layout label of the subtype.
+ {@link android.view.inputmethod.InputMethodSubtype#getLayoutDisplayName} returns the
+ value specified in this attribute. -->
+ <attr name="layoutLabel" format="reference" />
<!-- The icon of the subtype. -->
<attr name="icon" />
<!-- The locale of the subtype. This string should be a locale (for example en_US and fr_FR)
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 779422a70cad..31e9913dd988 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -472,10 +472,13 @@
<integer name="config_mt_sms_polling_throttle_millis">300000</integer>
<java-symbol type="integer" name="config_mt_sms_polling_throttle_millis" />
-
<!-- The receiver class of the intent that hidden menu sends to start satellite non-emergency mode -->
<string name="config_satellite_carrier_roaming_non_emergency_session_class" translatable="false"></string>
<java-symbol type="string" name="config_satellite_carrier_roaming_non_emergency_session_class" />
+ <!-- Whether to show the system notification to users whenever there is a change
+ in the satellite availability state at the current location. -->
+ <bool name="config_satellite_should_notify_availability">false</bool>
+ <java-symbol type="bool" name="config_satellite_should_notify_availability" />
</resources>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 0c28ea406aa2..70cc5f14391d 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -125,6 +125,8 @@
<public name="supplementalDescription"/>
<!-- @FlaggedApi("android.security.enable_intent_matching_flags") -->
<public name="intentMatchingFlags"/>
+ <!-- @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API) -->
+ <public name="layoutLabel"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01b60000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d3ef07ca8122..e23e665e7335 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -6522,6 +6522,54 @@ ul.</string>
<string name="satellite_manual_selection_state_popup_cancel">Go back</string>
<!-- Initial/System provided label shown for an app which gets unarchived. [CHAR LIMIT=64]. -->
<string name="unarchival_session_app_label">Pending...</string>
+ <!-- Notification title when satellite service is available. -->
+ <string name="satellite_sos_available_notification_title">Satellite SOS is now available</string>
+ <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_available_notification_summary">You can message emergency services if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+ <!-- Notification title when satellite service is not supported by device. -->
+ <string name="satellite_sos_not_supported_notification_title">Satellite SOS isn\'t supported</string>
+ <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_supported_notification_summary">Satellite SOS isn\'t supported on this device</string>
+ <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_provisioned_notification_title">Satellite SOS isn\'t set up</string>
+ <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+ <!-- Notification title when satellite service is not allowed at current location. -->
+ <string name="satellite_sos_not_in_allowed_region_notification_title">Satellite SOS isn\'t available</string>
+ <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_not_in_allowed_region_notification_summary">Satellite SOS isn\'t available in this country or region</string>
+ <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_unsupported_default_sms_app_notification_title">Satellite SOS not set up</string>
+ <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+ <!-- Notification title when location settings is disabled. -->
+ <string name="satellite_sos_location_disabled_notification_title">Satellite SOS isn\'t available</string>
+ <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_sos_location_disabled_notification_summary">To check if satellite SOS is available in this country or region, turn on location settings</string>
+ <!-- Notification title when satellite service is available. -->
+ <string name="satellite_messaging_available_notification_title">Satellite messaging available</string>
+ <!-- Notification summary when satellite service is available. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_available_notification_summary">You can message by satellite if there\'s no mobile or Wi-Fi network. Google Messages must be your default messaging app.</string>
+ <!-- Notification title when satellite service is not supported by device. -->
+ <string name="satellite_messaging_not_supported_notification_title">Satellite messaging not supported</string>
+ <!-- Notification summary when satellite service is not supported by device. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_supported_notification_summary">Satellite messaging isn\'t supported on this device</string>
+ <!-- Notification title when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_provisioned_notification_title">Satellite messaging not set up</string>
+ <!-- Notification summary when satellite service is not provisioned. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_provisioned_notification_summary">Make sure you\'re connected to the internet and try setup again</string>
+ <!-- Notification title when satellite service is not allowed at current location. -->
+ <string name="satellite_messaging_not_in_allowed_region_notification_title">Satellite messaging not available</string>
+ <!-- Notification summary when satellite service is not allowed at current location. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_not_in_allowed_region_notification_summary">Satellite messaging isn\'t available in this country or region</string>
+ <!-- Notification title when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_title">Satellite messaging not set up</string>
+ <!-- Notification summary when default messaging app does not support satellite. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_unsupported_default_sms_app_notification_summary">To message by satellite, set Google Messages as your default messaging app</string>
+ <!-- Notification title when location settings is disabled. -->
+ <string name="satellite_messaging_location_disabled_notification_title">Satellite messaging not available</string>
+ <!-- Notification summary when location settings is disabled. [CHAR LIMIT=NONE] -->
+ <string name="satellite_messaging_location_disabled_notification_summary">To check if satellite messaging is available in this country or region, turn on location settings</string>
<!-- Fingerprint dangling notification title -->
<string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b7cb1981b2f2..fec8bbbfeb83 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5552,6 +5552,30 @@
<java-symbol type="string" name="satellite_manual_selection_state_popup_cancel" />
<java-symbol type="drawable" name="ic_satellite_alt_24px" />
<java-symbol type="drawable" name="ic_android_satellite_24px" />
+ <java-symbol type="string" name="satellite_sos_available_notification_title" />
+ <java-symbol type="string" name="satellite_sos_available_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_in_allowed_region_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_supported_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_supported_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_not_provisioned_notification_title" />
+ <java-symbol type="string" name="satellite_sos_not_provisioned_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_title" />
+ <java-symbol type="string" name="satellite_sos_unsupported_default_sms_app_notification_summary" />
+ <java-symbol type="string" name="satellite_sos_location_disabled_notification_title" />
+ <java-symbol type="string" name="satellite_sos_location_disabled_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_available_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_available_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_in_allowed_region_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_supported_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_supported_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_not_provisioned_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_unsupported_default_sms_app_notification_summary" />
+ <java-symbol type="string" name="satellite_messaging_location_disabled_notification_title" />
+ <java-symbol type="string" name="satellite_messaging_location_disabled_notification_summary" />
<!-- DisplayManager configs. -->
<java-symbol type="bool" name="config_evenDimmerEnabled" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 24f6ceaf786c..8d045f87063b 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -26,6 +26,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -59,6 +60,7 @@ import android.app.servertransaction.ResumeActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -67,7 +69,11 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Looper;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -129,6 +135,9 @@ public class ActivityThreadTest {
@Rule(order = 1)
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
private ActivityWindowInfoListener mActivityWindowInfoListener;
private WindowTokenClientController mOriginalWindowTokenClientController;
private Configuration mOriginalAppConfig;
@@ -912,6 +921,32 @@ public class ActivityThreadTest {
}
/**
+ * Verifies that {@link ActivityThread#handleApplicationInfoChanged} does send updates to the
+ * system context, when given the system application info.
+ */
+ @RequiresFlagsEnabled(android.content.res.Flags.FLAG_SYSTEM_CONTEXT_HANDLE_APP_INFO_CHANGED)
+ @Test
+ public void testHandleApplicationInfoChanged_systemContext() {
+ Looper.prepare();
+ final var systemThread = ActivityThread.createSystemActivityThreadForTesting();
+
+ final Context systemContext = systemThread.getSystemContext();
+ final var appInfo = systemContext.getApplicationInfo();
+ // sourceDir must not be null, and contain at least a '/', for handleApplicationInfoChanged.
+ appInfo.sourceDir = "/";
+
+ // Create a copy of the application info.
+ final var newAppInfo = new ApplicationInfo(appInfo);
+ newAppInfo.sourceDir = "/";
+ assertWithMessage("New application info is a separate instance")
+ .that(systemContext.getApplicationInfo()).isNotSameInstanceAs(newAppInfo);
+
+ systemThread.handleApplicationInfoChanged(newAppInfo);
+ assertWithMessage("Application info was updated successfully")
+ .that(systemContext.getApplicationInfo()).isSameInstanceAs(newAppInfo);
+ }
+
+ /**
* Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
* Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
* activity for the given sequence number.
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
index 120a4de54427..3239598eccdc 100644
--- a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -301,18 +301,14 @@ public class KernelSingleUidTimeReaderTest {
{1_000_000, 2_000_000, 3_000_000},
{4_000_000, 5_000_000}});
- LongArrayMultiStateCounter.LongArrayContainer array =
- new LongArrayMultiStateCounter.LongArrayContainer(5);
+
long[] out = new long[5];
- success = mInjector.addDelta(TEST_UID, counter, 2000, array);
+ success = mInjector.addDelta(TEST_UID, counter, 2000, out);
assertThat(success).isTrue();
-
- array.getValues(out);
assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
- counter.getCounts(array, 0);
- array.getValues(out);
+ counter.getCounts(out, 0);
assertThat(out).isEqualTo(new long[]{1, 2, 3, 4, 5});
counter.setState(1, 3000);
@@ -322,18 +318,14 @@ public class KernelSingleUidTimeReaderTest {
{11_000_000, 22_000_000, 33_000_000},
{44_000_000, 55_000_000}});
- success = mInjector.addDelta(TEST_UID, counter, 4000, array);
+ success = mInjector.addDelta(TEST_UID, counter, 4000, out);
assertThat(success).isTrue();
-
- array.getValues(out);
assertThat(out).isEqualTo(new long[]{10, 20, 30, 40, 50});
- counter.getCounts(array, 0);
- array.getValues(out);
+ counter.getCounts(out, 0);
assertThat(out).isEqualTo(new long[]{1 + 5, 2 + 10, 3 + 15, 4 + 20, 5 + 25});
- counter.getCounts(array, 1);
- array.getValues(out);
+ counter.getCounts(out, 1);
assertThat(out).isEqualTo(new long[]{5, 10, 15, 20, 25});
}
@@ -385,7 +377,7 @@ public class KernelSingleUidTimeReaderTest {
@Override
public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
- LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
+ long[] deltaOut) {
return addDeltaForTest(uid, counter, timestampMs, mCpuTimeInStatePerClusterNs,
deltaOut);
}
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index b86dc5807b22..7e5d0a4c2e42 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -24,14 +24,11 @@ import android.os.BadParcelableException;
import android.os.Parcel;
import android.platform.test.ravenwood.RavenwoodRule;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.runner.RunWith;
-@RunWith(AndroidJUnit4.class)
@SmallTest
public class LongArrayMultiStateCounterTest {
@Rule
@@ -41,11 +38,11 @@ public class LongArrayMultiStateCounterTest {
public void setStateAndUpdateValue() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
counter.setState(0, 1000);
counter.setState(1, 2000);
counter.setState(0, 4000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 9000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 9000);
assertCounts(counter, 0, new long[]{75, 150, 225, 300});
assertCounts(counter, 1, new long[]{25, 50, 75, 100});
@@ -55,15 +52,28 @@ public class LongArrayMultiStateCounterTest {
}
@Test
+ public void increment() {
+ LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.setState(0, 1000);
+ counter.incrementValues(new long[]{1, 2, 3, 4}, 2000);
+ counter.incrementValues(new long[]{100, 200, 300, 400}, 3000);
+
+ assertCounts(counter, 0, new long[]{101, 202, 303, 404});
+ assertCounts(counter, 1, new long[]{0, 0, 0, 0});
+ }
+
+ @Test
public void copyStatesFrom() {
LongArrayMultiStateCounter source = new LongArrayMultiStateCounter(2, 1);
- updateValue(source, new long[]{0}, 1000);
+ source.updateValues(new long[]{0}, 1000);
source.setState(0, 1000);
source.setState(1, 2000);
LongArrayMultiStateCounter target = new LongArrayMultiStateCounter(2, 1);
target.copyStatesFrom(source);
- updateValue(target, new long[]{1000}, 5000);
+ target.updateValues(new long[]{1000}, 5000);
assertCounts(target, 0, new long[]{250});
assertCounts(target, 1, new long[]{750});
@@ -83,25 +93,25 @@ public class LongArrayMultiStateCounterTest {
public void setEnabled() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
counter.setState(0, 1000);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
counter.setEnabled(false, 3000);
// Partially included, because the counter is disabled after the previous update
- updateValue(counter, new long[]{200, 300, 400, 500}, 4000);
+ counter.updateValues(new long[]{200, 300, 400, 500}, 4000);
// Count only 50%, because the counter was disabled for 50% of the time
assertCounts(counter, 0, new long[]{150, 250, 350, 450});
// Not counted because the counter is disabled
- updateValue(counter, new long[]{250, 350, 450, 550}, 5000);
+ counter.updateValues(new long[]{250, 350, 450, 550}, 5000);
counter.setEnabled(true, 6000);
- updateValue(counter, new long[]{300, 400, 500, 600}, 7000);
+ counter.updateValues(new long[]{300, 400, 500, 600}, 7000);
// Again, take 50% of the delta
assertCounts(counter, 0, new long[]{175, 275, 375, 475});
@@ -111,8 +121,8 @@ public class LongArrayMultiStateCounterTest {
public void reset() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
counter.setState(0, 1000);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
@@ -120,8 +130,8 @@ public class LongArrayMultiStateCounterTest {
assertCounts(counter, 0, new long[]{0, 0, 0, 0});
- updateValue(counter, new long[]{200, 300, 400, 500}, 3000);
- updateValue(counter, new long[]{300, 400, 500, 600}, 4000);
+ counter.updateValues(new long[]{200, 300, 400, 500}, 3000);
+ counter.updateValues(new long[]{300, 400, 500, 600}, 4000);
assertCounts(counter, 0, new long[]{100, 100, 100, 100});
}
@@ -129,11 +139,11 @@ public class LongArrayMultiStateCounterTest {
@Test
public void parceling() {
LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
- updateValue(counter, new long[]{0, 0, 0, 0}, 1000);
+ counter.updateValues(new long[]{0, 0, 0, 0}, 1000);
counter.setState(0, 1000);
- updateValue(counter, new long[]{100, 200, 300, 400}, 2000);
+ counter.updateValues(new long[]{100, 200, 300, 400}, 2000);
counter.setState(1, 2000);
- updateValue(counter, new long[]{101, 202, 304, 408}, 3000);
+ counter.updateValues(new long[]{101, 202, 304, 408}, 3000);
assertCounts(counter, 0, new long[]{100, 200, 300, 400});
assertCounts(counter, 1, new long[]{1, 2, 4, 8});
@@ -158,27 +168,17 @@ public class LongArrayMultiStateCounterTest {
// State, last update timestamp and current counts are undefined at this point.
newCounter.setState(0, 100);
- updateValue(newCounter, new long[]{300, 400, 500, 600}, 100);
+ newCounter.updateValues(new long[]{300, 400, 500, 600}, 100);
// A new base state and counters are established; we can continue accumulating deltas
- updateValue(newCounter, new long[]{316, 432, 564, 728}, 200);
+ newCounter.updateValues(new long[]{316, 432, 564, 728}, 200);
assertCounts(newCounter, 0, new long[]{116, 232, 364, 528});
}
- private void updateValue(LongArrayMultiStateCounter counter, long[] values, int timestamp) {
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(values.length);
- container.setValues(values);
- counter.updateValues(container, timestamp);
- }
-
private void assertCounts(LongArrayMultiStateCounter counter, int state, long[] expected) {
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(expected.length);
long[] counts = new long[expected.length];
- counter.getCounts(container, state);
- container.getValues(counts);
+ counter.getCounts(counts, state);
assertThat(counts).isEqualTo(expected);
}
@@ -230,33 +230,4 @@ public class LongArrayMultiStateCounterTest {
parcel.writeInt(endPos - startPos);
parcel.setDataPosition(endPos);
}
-
- @Test
- public void combineValues() {
- long[] values = new long[] {0, 1, 2, 3, 42};
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(values.length);
- container.setValues(values);
-
- long[] out = new long[3];
- int[] indexes = {2, 1, 1, 0, 0};
- boolean nonZero = container.combineValues(out, indexes);
- assertThat(nonZero).isTrue();
- assertThat(out).isEqualTo(new long[]{45, 3, 0});
-
- // All zeros
- container.setValues(new long[]{0, 0, 0, 0, 0});
- nonZero = container.combineValues(out, indexes);
- assertThat(nonZero).isFalse();
- assertThat(out).isEqualTo(new long[]{0, 0, 0});
-
- // Index out of range
- IndexOutOfBoundsException e1 = assertThrows(
- IndexOutOfBoundsException.class,
- () -> container.combineValues(out, new int[]{0, 1, -1, 0, 0}));
- assertThat(e1.getMessage()).isEqualTo("Index -1 is out of bounds: [0, 2]");
- IndexOutOfBoundsException e2 = assertThrows(IndexOutOfBoundsException.class,
- () -> container.combineValues(out, new int[]{0, 1, 4, 0, 0}));
- assertThat(e2.getMessage()).isEqualTo("Index 4 is out of bounds: [0, 2]");
- }
}
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index 93d94c9cd7eb..b4899f975f43 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -19,6 +19,8 @@ package android.graphics;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
+import com.android.internal.camera.flags.Flags;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -63,6 +65,7 @@ public class ImageFormat {
RAW_DEPTH10,
PRIVATE,
HEIC,
+ HEIC_ULTRAHDR,
JPEG_R
})
public @interface Format {
@@ -832,6 +835,16 @@ public class ImageFormat {
public static final int HEIC = 0x48454946;
/**
+ * High Efficiency Image File Format (HEIF) with embedded HDR gain map
+ *
+ * <p>This format defines the HEIC brand of High Efficiency Image File
+ * Format as described in ISO/IEC 23008-12:2024 with HDR gain map according
+ * to ISO/CD 21496‐1.</p>
+ */
+ @FlaggedApi(Flags.FLAG_CAMERA_HEIF_GAINMAP)
+ public static final int HEIC_ULTRAHDR = 0x1006;
+
+ /**
* Use this function to retrieve the number of bits per pixel of an
* ImageFormat.
*
@@ -926,6 +939,11 @@ public class ImageFormat {
if (android.media.codec.Flags.p210FormatSupport() && format == YCBCR_P210) {
return true;
}
+ if (Flags.cameraHeifGainmap()){
+ if (format == HEIC_ULTRAHDR) {
+ return true;
+ }
+ }
return false;
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 438532725686..4d7be39ca5a4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -16,6 +16,7 @@
package androidx.window.extensions.area;
+import static android.hardware.devicestate.DeviceState.PROPERTY_EMULATED_ONLY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_DUAL_DISPLAY_INTERNAL_DEFAULT;
import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
@@ -569,7 +570,8 @@ public class WindowAreaComponentImpl implements WindowAreaComponent,
private boolean isDeviceFolded() {
if (Flags.deviceStatePropertyApi()) {
return mCurrentDeviceState.hasProperty(
- PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
+ && !mCurrentDeviceState.hasProperty(PROPERTY_EMULATED_ONLY);
} else {
return ArrayUtils.contains(mFoldedDeviceStates, mCurrentDeviceState.getIdentifier());
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
index 2fbf089d99d6..00d9a931cebe 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerViewTest.kt
@@ -19,12 +19,15 @@ package com.android.wm.shell.bubbles.bar
import android.app.ActivityManager
import android.content.Context
import android.content.pm.LauncherApps
+import android.graphics.PointF
import android.os.Handler
import android.os.UserManager
import android.view.IWindowManager
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
+import androidx.core.animation.AnimatorTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -48,6 +51,7 @@ import com.android.wm.shell.bubbles.BubbleTaskViewFactory
import com.android.wm.shell.bubbles.Bubbles.SysuiProxy
import com.android.wm.shell.bubbles.FakeBubbleFactory
import com.android.wm.shell.bubbles.UiEventSubject.Companion.assertThat
+import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix
import com.android.wm.shell.bubbles.properties.BubbleProperties
import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
import com.android.wm.shell.common.DisplayController
@@ -57,6 +61,7 @@ import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.common.TaskStackListenerImpl
import com.android.wm.shell.shared.TransactionPool
+import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
@@ -66,8 +71,10 @@ import com.android.wm.shell.taskview.TaskViewTaskController
import com.android.wm.shell.taskview.TaskViewTransitions
import com.android.wm.shell.transition.Transitions
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import java.util.Collections
import org.junit.Before
+import org.junit.ClassRule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@@ -79,18 +86,28 @@ import org.mockito.kotlin.whenever
@RunWith(AndroidJUnit4::class)
class BubbleBarLayerViewTest {
+ companion object {
+ @JvmField @ClassRule
+ val animatorTestRule: AnimatorTestRule = AnimatorTestRule()
+ }
+
private val context = ApplicationProvider.getApplicationContext<Context>()
private lateinit var bubbleBarLayerView: BubbleBarLayerView
private lateinit var uiEventLoggerFake: UiEventLoggerFake
+ private lateinit var bubbleController: BubbleController
+
+ private lateinit var bubblePositioner: BubblePositioner
+
private lateinit var bubble: Bubble
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
ProtoLog.init()
+ PhysicsAnimatorTestUtils.prepareForTest()
uiEventLoggerFake = UiEventLoggerFake()
val bubbleLogger = BubbleLogger(uiEventLoggerFake)
@@ -100,7 +117,7 @@ class BubbleBarLayerViewTest {
val windowManager = context.getSystemService(WindowManager::class.java)
- val bubblePositioner = BubblePositioner(context, windowManager)
+ bubblePositioner = BubblePositioner(context, windowManager)
bubblePositioner.setShowingInBubbleBar(true)
val bubbleData =
@@ -113,7 +130,7 @@ class BubbleBarLayerViewTest {
bgExecutor,
)
- val bubbleController =
+ bubbleController =
createBubbleController(
bubbleData,
windowManager,
@@ -151,6 +168,12 @@ class BubbleBarLayerViewTest {
bubble = FakeBubbleFactory.createChatBubbleWithViewInfo(context, viewInfo = viewInfo)
}
+ @After
+ fun tearDown() {
+ PhysicsAnimatorTestUtils.tearDown()
+ getInstrumentation().waitForIdleSync()
+ }
+
private fun createBubbleController(
bubbleData: BubbleData,
windowManager: WindowManager?,
@@ -224,6 +247,70 @@ class BubbleBarLayerViewTest {
assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
}
+ @Test
+ fun testEventLogging_dragExpandedViewLeft() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.RIGHT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ // Drag from right to left
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, rightEdge())
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, leftEdge())
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, leftEdge())
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ @Test
+ fun testEventLogging_dragExpandedViewRight() {
+ bubblePositioner.bubbleBarLocation = BubbleBarLocation.LEFT
+
+ getInstrumentation().runOnMainSync {
+ bubbleBarLayerView.showExpandedView(bubble)
+ }
+ waitForExpandedViewAnimation()
+
+ val handleView = bubbleBarLayerView.findViewById<View>(R.id.bubble_bar_handle_view)
+ assertThat(handleView).isNotNull()
+
+ // Drag from left to right
+ handleView.dispatchTouchEvent(0L, MotionEvent.ACTION_DOWN, leftEdge())
+ handleView.dispatchTouchEvent(10L, MotionEvent.ACTION_MOVE, rightEdge())
+ handleView.dispatchTouchEvent(20L, MotionEvent.ACTION_UP, rightEdge())
+
+ assertThat(uiEventLoggerFake.numLogs()).isEqualTo(1)
+ assertThat(uiEventLoggerFake.logs[0].eventId)
+ .isEqualTo(BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW.id)
+ assertThat(uiEventLoggerFake.logs[0]).hasBubbleInfo(bubble)
+ }
+
+ private fun leftEdge(): PointF {
+ val screenSize = bubblePositioner.availableRect
+ return PointF(screenSize.left.toFloat(), screenSize.height() / 2f)
+ }
+
+ private fun rightEdge(): PointF {
+ val screenSize = bubblePositioner.availableRect
+ return PointF(screenSize.right.toFloat(), screenSize.height() / 2f)
+ }
+
+ private fun waitForExpandedViewAnimation() {
+ // wait for idle to allow the animation to start
+ getInstrumentation().waitForIdleSync()
+ getInstrumentation().runOnMainSync { animatorTestRule.advanceTimeBy(200) }
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(
+ AnimatableScaleMatrix.SCALE_X, AnimatableScaleMatrix.SCALE_Y)
+ }
+
private inner class FakeBubbleTaskViewFactory(private val mainExecutor: ShellExecutor) :
BubbleTaskViewFactory {
override fun create(): BubbleTaskView {
@@ -290,4 +377,9 @@ class BubbleBarLayerViewTest {
}
}
}
+
+ private fun View.dispatchTouchEvent(eventTime: Long, action: Int, point: PointF) {
+ val event = MotionEvent.obtain(0L, eventTime, action, point.x, point.y, 0)
+ getInstrumentation().runOnMainSync { dispatchTouchEvent(event) }
+ }
}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index ecb2b25a02f1..d4cbe6e10971 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -74,6 +74,7 @@ class BubbleExpandedViewPinControllerTest {
@Before
fun setUp() {
ProtoLog.REQUIRE_PROTOLOGTOOL = false
+ ProtoLog.init()
container = FrameLayout(context)
val windowManager = context.getSystemService(WindowManager::class.java)
positioner = BubblePositioner(context, windowManager)
@@ -85,7 +86,7 @@ class BubbleExpandedViewPinControllerTest {
isSmallTablet = false,
isLandscape = true,
isRtl = false,
- insets = Insets.of(10, 20, 30, 40)
+ insets = Insets.of(10, 20, 30, 40),
)
positioner.update(deviceConfig)
positioner.bubbleBarTopOnScreen =
@@ -407,12 +408,26 @@ class BubbleExpandedViewPinControllerTest {
assertThat(testListener.locationReleases).containsExactly(RIGHT)
}
+ /** Send drag start event when on left */
+ @Test
+ fun start_onLeft_sendStartEventOnLeft() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = true) }
+ assertThat(testListener.locationStart).containsExactly(LEFT)
+ }
+
+ /** Send drag start event when on right */
+ @Test
+ fun start_onRight_sendStartEventOnRight() {
+ getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) }
+ assertThat(testListener.locationStart).containsExactly(RIGHT)
+ }
+
private fun getExpectedDropTargetBoundsOnLeft(): Rect =
Rect().also {
positioner.getBubbleBarExpandedViewBounds(
true /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -421,7 +436,7 @@ class BubbleExpandedViewPinControllerTest {
positioner.getBubbleBarExpandedViewBounds(
false /* onLeft */,
false /* isOverflowExpanded */,
- it
+ it,
)
}
@@ -446,8 +461,14 @@ class BubbleExpandedViewPinControllerTest {
}
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
+ val locationStart = mutableListOf<BubbleBarLocation>()
val locationChanges = mutableListOf<BubbleBarLocation>()
val locationReleases = mutableListOf<BubbleBarLocation>()
+
+ override fun onStart(location: BubbleBarLocation) {
+ locationStart.add(location)
+ }
+
override fun onChange(location: BubbleBarLocation) {
locationChanges.add(location)
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
index e21bf8fb723c..93e635dd937c 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.aidl
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.aidl
@@ -16,4 +16,4 @@
package com.android.wm.shell.shared;
-parcelable GroupedRecentTaskInfo; \ No newline at end of file
+parcelable GroupedTaskInfo; \ No newline at end of file
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
index 65e079ef4f72..03e0ab0591a1 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/GroupedTaskInfo.java
@@ -17,7 +17,8 @@
package com.android.wm.shell.shared;
import android.annotation.IntDef;
-import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
+import android.app.TaskInfo;
import android.app.WindowConfiguration;
import android.os.Parcel;
import android.os.Parcelable;
@@ -27,69 +28,91 @@ import androidx.annotation.Nullable;
import com.android.wm.shell.shared.split.SplitBounds;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
- * Simple container for recent tasks. May contain either a single or pair of tasks.
+ * Simple container for recent tasks which should be presented as a single task within the
+ * Overview UI.
*/
-public class GroupedRecentTaskInfo implements Parcelable {
+public class GroupedTaskInfo implements Parcelable {
- public static final int TYPE_SINGLE = 1;
+ public static final int TYPE_FULLSCREEN = 1;
public static final int TYPE_SPLIT = 2;
public static final int TYPE_FREEFORM = 3;
@IntDef(prefix = {"TYPE_"}, value = {
- TYPE_SINGLE,
+ TYPE_FULLSCREEN,
TYPE_SPLIT,
TYPE_FREEFORM
})
public @interface GroupType {}
+ /**
+ * The type of this particular task info, can be one of TYPE_FULLSCREEN, TYPE_SPLIT or
+ * TYPE_FREEFORM.
+ */
+ @GroupType
+ protected final int mType;
+
+ /**
+ * The list of tasks associated with this single recent task info.
+ * TYPE_FULLSCREEN: Contains the stack of tasks associated with a single "task" in overview
+ * TYPE_SPLIT: Contains the two split roots of each side
+ * TYPE_FREEFORM: Contains the set of tasks currently in freeform mode
+ */
@NonNull
- private final ActivityManager.RecentTaskInfo[] mTasks;
+ protected final List<TaskInfo> mTasks;
+
+ /**
+ * Only set for TYPE_SPLIT.
+ *
+ * Information about the split bounds.
+ */
@Nullable
- private final SplitBounds mSplitBounds;
- @GroupType
- private final int mType;
- // TODO(b/348332802): move isMinimized inside each Task object instead once we have a
- // replacement for RecentTaskInfo
- private final int[] mMinimizedTaskIds;
+ protected final SplitBounds mSplitBounds;
/**
- * Create new for a single task
+ * Only set for TYPE_FREEFORM.
+ *
+ * TODO(b/348332802): move isMinimized inside each Task object instead once we have a
+ * replacement for RecentTaskInfo
*/
- public static GroupedRecentTaskInfo forSingleTask(
- @NonNull ActivityManager.RecentTaskInfo task) {
- return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task}, null,
- TYPE_SINGLE, null /* minimizedFreeformTasks */);
+ @Nullable
+ protected final int[] mMinimizedTaskIds;
+
+ /**
+ * Create new for a stack of fullscreen tasks
+ */
+ public static GroupedTaskInfo forFullscreenTasks(@NonNull TaskInfo task) {
+ return new GroupedTaskInfo(List.of(task), null, TYPE_FULLSCREEN,
+ null /* minimizedFreeformTasks */);
}
/**
* Create new for a pair of tasks in split screen
*/
- public static GroupedRecentTaskInfo forSplitTasks(@NonNull ActivityManager.RecentTaskInfo task1,
- @NonNull ActivityManager.RecentTaskInfo task2, @Nullable SplitBounds splitBounds) {
- return new GroupedRecentTaskInfo(new ActivityManager.RecentTaskInfo[]{task1, task2},
- splitBounds, TYPE_SPLIT, null /* minimizedFreeformTasks */);
+ public static GroupedTaskInfo forSplitTasks(@NonNull TaskInfo task1,
+ @NonNull TaskInfo task2, @Nullable SplitBounds splitBounds) {
+ return new GroupedTaskInfo(List.of(task1, task2), splitBounds, TYPE_SPLIT,
+ null /* minimizedFreeformTasks */);
}
/**
* Create new for a group of freeform tasks
*/
- public static GroupedRecentTaskInfo forFreeformTasks(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
- @NonNull Set<Integer> minimizedFreeformTasks) {
- return new GroupedRecentTaskInfo(
- tasks,
- null /* splitBounds */,
- TYPE_FREEFORM,
+ public static GroupedTaskInfo forFreeformTasks(
+ @NonNull List<TaskInfo> tasks,
+ @NonNull Set<Integer> minimizedFreeformTasks) {
+ return new GroupedTaskInfo(tasks, null /* splitBounds */, TYPE_FREEFORM,
minimizedFreeformTasks.stream().mapToInt(i -> i).toArray());
}
- private GroupedRecentTaskInfo(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ private GroupedTaskInfo(
+ @NonNull List<TaskInfo> tasks,
@Nullable SplitBounds splitBounds,
@GroupType int type,
@Nullable int[] minimizedFreeformTaskIds) {
@@ -100,52 +123,56 @@ public class GroupedRecentTaskInfo implements Parcelable {
ensureAllMinimizedIdsPresent(tasks, minimizedFreeformTaskIds);
}
- private static void ensureAllMinimizedIdsPresent(
- @NonNull ActivityManager.RecentTaskInfo[] tasks,
+ private void ensureAllMinimizedIdsPresent(
+ @NonNull List<TaskInfo> tasks,
@Nullable int[] minimizedFreeformTaskIds) {
if (minimizedFreeformTaskIds == null) {
return;
}
if (!Arrays.stream(minimizedFreeformTaskIds).allMatch(
- taskId -> Arrays.stream(tasks).anyMatch(task -> task.taskId == taskId))) {
+ taskId -> tasks.stream().anyMatch(task -> task.taskId == taskId))) {
throw new IllegalArgumentException("Minimized task IDs contain non-existent Task ID.");
}
}
- GroupedRecentTaskInfo(Parcel parcel) {
- mTasks = parcel.createTypedArray(ActivityManager.RecentTaskInfo.CREATOR);
+ protected GroupedTaskInfo(@NonNull Parcel parcel) {
+ mTasks = new ArrayList();
+ final int numTasks = parcel.readInt();
+ for (int i = 0; i < numTasks; i++) {
+ mTasks.add(new TaskInfo(parcel));
+ }
mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
mType = parcel.readInt();
mMinimizedTaskIds = parcel.createIntArray();
}
/**
- * Get primary {@link ActivityManager.RecentTaskInfo}
+ * Get primary {@link RecentTaskInfo}
*/
@NonNull
- public ActivityManager.RecentTaskInfo getTaskInfo1() {
- return mTasks[0];
+ public TaskInfo getTaskInfo1() {
+ return mTasks.getFirst();
}
/**
- * Get secondary {@link ActivityManager.RecentTaskInfo}.
+ * Get secondary {@link RecentTaskInfo}.
*
* Used in split screen.
*/
@Nullable
- public ActivityManager.RecentTaskInfo getTaskInfo2() {
- if (mTasks.length > 1) {
- return mTasks[1];
+ public TaskInfo getTaskInfo2() {
+ if (mTasks.size() > 1) {
+ return mTasks.get(1);
}
return null;
}
/**
- * Get all {@link ActivityManager.RecentTaskInfo}s grouped together.
+ * Get all {@link RecentTaskInfo}s grouped together.
*/
@NonNull
- public List<ActivityManager.RecentTaskInfo> getTaskInfoList() {
- return Arrays.asList(mTasks);
+ public List<TaskInfo> getTaskInfoList() {
+ return mTasks;
}
/**
@@ -164,28 +191,46 @@ public class GroupedRecentTaskInfo implements Parcelable {
return mType;
}
+ @Nullable
public int[] getMinimizedTaskIds() {
return mMinimizedTaskIds;
}
@Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof GroupedTaskInfo)) {
+ return false;
+ }
+ GroupedTaskInfo other = (GroupedTaskInfo) obj;
+ return mType == other.mType
+ && Objects.equals(mTasks, other.mTasks)
+ && Objects.equals(mSplitBounds, other.mSplitBounds)
+ && Arrays.equals(mMinimizedTaskIds, other.mMinimizedTaskIds);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mType, mTasks, mSplitBounds, Arrays.hashCode(mMinimizedTaskIds));
+ }
+
+ @Override
public String toString() {
StringBuilder taskString = new StringBuilder();
- for (int i = 0; i < mTasks.length; i++) {
+ for (int i = 0; i < mTasks.size(); i++) {
if (i == 0) {
taskString.append("Task");
} else {
taskString.append(", Task");
}
- taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks[i]));
+ taskString.append(i + 1).append(": ").append(getTaskInfo(mTasks.get(i)));
}
if (mSplitBounds != null) {
taskString.append(", SplitBounds: ").append(mSplitBounds);
}
taskString.append(", Type=");
switch (mType) {
- case TYPE_SINGLE:
- taskString.append("TYPE_SINGLE");
+ case TYPE_FULLSCREEN:
+ taskString.append("TYPE_FULLSCREEN");
break;
case TYPE_SPLIT:
taskString.append("TYPE_SPLIT");
@@ -199,7 +244,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
return taskString.toString();
}
- private String getTaskInfo(ActivityManager.RecentTaskInfo taskInfo) {
+ private String getTaskInfo(TaskInfo taskInfo) {
if (taskInfo == null) {
return null;
}
@@ -213,7 +258,12 @@ public class GroupedRecentTaskInfo implements Parcelable {
@Override
public void writeToParcel(Parcel parcel, int flags) {
- parcel.writeTypedArray(mTasks, flags);
+ // We don't use the parcel list methods because we want to only write the TaskInfo state
+ // and not the subclasses (Recents/RunningTaskInfo) whose fields are all deprecated
+ parcel.writeInt(mTasks.size());
+ for (int i = 0; i < mTasks.size(); i++) {
+ mTasks.get(i).writeTaskToParcel(parcel, flags);
+ }
parcel.writeTypedObject(mSplitBounds, flags);
parcel.writeInt(mType);
parcel.writeIntArray(mMinimizedTaskIds);
@@ -224,13 +274,15 @@ public class GroupedRecentTaskInfo implements Parcelable {
return 0;
}
- public static final @android.annotation.NonNull Creator<GroupedRecentTaskInfo> CREATOR =
- new Creator<GroupedRecentTaskInfo>() {
- public GroupedRecentTaskInfo createFromParcel(Parcel source) {
- return new GroupedRecentTaskInfo(source);
+ public static final Creator<GroupedTaskInfo> CREATOR = new Creator() {
+ @Override
+ public GroupedTaskInfo createFromParcel(Parcel in) {
+ return new GroupedTaskInfo(in);
}
- public GroupedRecentTaskInfo[] newArray(int size) {
- return new GroupedRecentTaskInfo[size];
+
+ @Override
+ public GroupedTaskInfo[] newArray(int size) {
+ return new GroupedTaskInfo[size];
}
};
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 88878c6adcf2..e033f673d07d 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -360,7 +360,7 @@ public class TransitionUtil {
windowConfiguration = new WindowConfiguration();
}
- Rect localBounds = new Rect();
+ Rect bounds = windowConfiguration.getBounds();
RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId,
newModeToLegacyMode(mode),
@@ -373,12 +373,12 @@ public class TransitionUtil {
new Rect(0, 0, 0, 0),
order,
null,
- localBounds,
- new Rect(),
+ bounds,
+ bounds,
windowConfiguration,
isNotInRecents,
null,
- new Rect(),
+ bounds,
taskInfo,
false,
INVALID_WINDOW_TYPE
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
index fc3dc1465dff..f93b35e868f6 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/PhysicsAnimatorTestUtils.kt
@@ -20,7 +20,7 @@ import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
import com.android.wm.shell.shared.animation.PhysicsAnimatorTestUtils.prepareForTest
-import java.util.*
+import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import kotlin.collections.ArrayList
@@ -74,14 +74,17 @@ object PhysicsAnimatorTestUtils {
@JvmStatic
fun tearDown() {
- val latch = CountDownLatch(1)
- animationThreadHandler.post {
+ if (Looper.myLooper() == animationThreadHandler.looper) {
animatorTestHelpers.keys.forEach { it.cancel() }
- latch.countDown()
+ } else {
+ val latch = CountDownLatch(1)
+ animationThreadHandler.post {
+ animatorTestHelpers.keys.forEach { it.cancel() }
+ latch.countDown()
+ }
+ latch.await(5, TimeUnit.SECONDS)
}
- latch.await()
-
animatorTestHelpers.clear()
animators.clear()
allAnimatedObjects.clear()
@@ -348,8 +351,9 @@ object PhysicsAnimatorTestUtils {
* Returns all of the values that have ever been reported to update listeners, per property.
*/
@Suppress("UNCHECKED_CAST")
- fun <T : Any> getAnimationUpdateFrames(animator: PhysicsAnimator<T>):
- UpdateFramesPerProperty<T> {
+ fun <T : Any> getAnimationUpdateFrames(
+ animator: PhysicsAnimator<T>
+ ): UpdateFramesPerProperty<T> {
return animatorTestHelpers[animator]?.getUpdates() as UpdateFramesPerProperty<T>
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
index 7086691e7431..bd129a28f049 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/bubbles/BaseBubblePinController.kt
@@ -56,6 +56,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
onLeft = initialLocationOnLeft
screenCenterX = screenSizeProvider.invoke().x / 2
dismissZone = getExclusionRect()
+ listener?.onStart(if (initialLocationOnLeft) LEFT else RIGHT)
}
/** View has moved to [x] and [y] screen coordinates */
@@ -109,6 +110,7 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
/** Get width for exclusion rect where dismiss takes over drag */
protected abstract fun getExclusionRectWidth(): Float
+
/** Get height for exclusion rect where dismiss takes over drag */
protected abstract fun getExclusionRectHeight(): Float
@@ -184,6 +186,9 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi
/** Receive updates on location changes */
interface LocationChangeListener {
+ /** Bubble bar dragging has started. Includes the initial location of the bar */
+ fun onStart(location: BubbleBarLocation) {}
+
/**
* Bubble bar has been dragged to a new [BubbleBarLocation]. And the drag is still in
* progress.
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
new file mode 100644
index 000000000000..20d5c33dc8bf
--- /dev/null
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/pip/OWNERS
@@ -0,0 +1,4 @@
+# WM shell sub-module PiP owner
+hwwang@google.com
+gabiyev@google.com
+wuperry@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index f59fed906e2d..dfe76b8543e1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -487,6 +487,20 @@ public class ShellTaskOrganizer extends TaskOrganizer {
return mHomeTaskOverlayContainer;
}
+ /**
+ * Returns the home task surface, not for wide use.
+ */
+ @Nullable
+ public SurfaceControl getHomeTaskSurface() {
+ for (int i = 0; i < mTasks.size(); i++) {
+ final TaskAppearedInfo info = mTasks.valueAt(i);
+ if (info.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+ return info.getLeash();
+ }
+ }
+ return null;
+ }
+
@Override
public void addStartingWindow(StartingWindowInfo info) {
if (mStartingWindow != null) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 5f0eed9daa1a..14f8cc74bfc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -1632,6 +1632,7 @@ public class BubbleController implements ConfigurationChangeListener,
if (!isShowingAsBubbleBar()) {
callback = b -> {
if (mStackView != null) {
+ b.setSuppressFlyout(true);
mStackView.addBubble(b);
mStackView.setSelectedBubble(b);
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 402818c80b01..999ce17905ef 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -124,18 +124,7 @@ public class BubbleBarLayerView extends FrameLayout
mBubbleExpandedViewPinController = new BubbleExpandedViewPinController(
context, this, mPositioner);
- mBubbleExpandedViewPinController.setListener(
- new BaseBubblePinController.LocationChangeListener() {
- @Override
- public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
- mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
- }
-
- @Override
- public void onRelease(@NonNull BubbleBarLocation location) {
- mBubbleController.setBubbleBarLocation(location);
- }
- });
+ mBubbleExpandedViewPinController.setListener(new LocationChangeListener());
setOnClickListener(view -> hideModalOrCollapse());
}
@@ -238,11 +227,7 @@ public class BubbleBarLayerView extends FrameLayout
DragListener dragListener = inDismiss -> {
if (inDismiss && mExpandedBubble != null) {
mBubbleController.dismissBubble(mExpandedBubble.getKey(), DISMISS_USER_GESTURE);
- if (mExpandedBubble instanceof Bubble) {
- // Only a bubble can be dragged to dismiss
- mBubbleLogger.log((Bubble) mExpandedBubble,
- BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
- }
+ logBubbleEvent(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_EXP_VIEW);
}
};
mDragController = new BubbleBarExpandedViewDragController(
@@ -423,10 +408,47 @@ public class BubbleBarLayerView extends FrameLayout
}
}
+ /**
+ * Log the event only if {@link #mExpandedBubble} is a {@link Bubble}.
+ * <p>
+ * Skips logging if it is {@link BubbleOverflow}.
+ */
+ private void logBubbleEvent(BubbleLogger.Event event) {
+ if (mExpandedBubble != null && mExpandedBubble instanceof Bubble bubble) {
+ mBubbleLogger.log(bubble, event);
+ }
+ }
+
@Nullable
@VisibleForTesting
public BubbleBarExpandedViewDragController getDragController() {
return mDragController;
}
+ private class LocationChangeListener implements
+ BaseBubblePinController.LocationChangeListener {
+
+ private BubbleBarLocation mInitialLocation;
+
+ @Override
+ public void onStart(@NonNull BubbleBarLocation location) {
+ mInitialLocation = location;
+ }
+
+ @Override
+ public void onChange(@NonNull BubbleBarLocation bubbleBarLocation) {
+ mBubbleController.animateBubbleBarLocation(bubbleBarLocation);
+ }
+
+ @Override
+ public void onRelease(@NonNull BubbleBarLocation location) {
+ mBubbleController.setBubbleBarLocation(location);
+ if (location != mInitialLocation) {
+ BubbleLogger.Event event = location.isOnLeft(isLayoutRtl())
+ ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_EXP_VIEW
+ : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_EXP_VIEW;
+ logBubbleEvent(event);
+ }
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
index b83b5f341dda..8ef20d1d6b93 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipUtils.kt
@@ -44,7 +44,8 @@ object PipUtils {
private const val TAG = "PipUtils"
// Minimum difference between two floats (e.g. aspect ratios) to consider them not equal.
- private const val EPSILON = 1e-7
+ // TODO b/377530560: Restore epsilon once a long term fix is merged for non-config-at-end issue.
+ private const val EPSILON = 0.05f
/**
* @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
index d1b2347a4411..62d5098f2a27 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/AppCompatUtils.kt
@@ -23,9 +23,15 @@ import android.content.Context
import com.android.internal.R
// TODO(b/347289970): Consider replacing with API
+/**
+ * If the top activity should be exempt from desktop windowing and forced back to fullscreen.
+ * Currently includes all system ui activities and modal dialogs. However is the top activity is not
+ * being displayed, regardless of its configuration, we will not exempt it as to remain in the
+ * desktop windowing environment.
+ */
fun isTopActivityExemptFromDesktopWindowing(context: Context, task: TaskInfo) =
- isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1
- && !task.isTopActivityStyleFloating)
+ (isSystemUiTask(context, task) || (task.isTopActivityTransparent && task.numActivities == 1))
+ && !task.isTopActivityNoDisplay
private fun isSystemUiTask(context: Context, task: TaskInfo): Boolean {
val sysUiPackageName: String =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 886330f3264a..0200e18b5c50 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -286,7 +286,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
// we need to ignore all the incoming TaskInfo until the education
// completes. If we come from a double tap we follow the normal flow.
final boolean topActivityPillarboxed =
- taskInfo.appCompatTaskInfo.isTopActivityPillarboxed();
+ taskInfo.appCompatTaskInfo.isTopActivityPillarboxShaped();
final boolean isFirstTimeHorizontalReachabilityEdu = topActivityPillarboxed
&& !mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(taskInfo);
final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed
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 2a5a519272c7..77e041ee7cdb 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
@@ -401,9 +401,6 @@ public abstract class WMShellBaseModule {
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
RootTaskDisplayAreaOrganizer rootTdaOrganizer) {
- if (!com.android.window.flags.Flags.explicitRefreshRateHints()) {
- return Optional.empty();
- }
final PerfHintController perfHintController =
new PerfHintController(context, shellInit, shellCommandHandler, rootTdaOrganizer);
return Optional.of(perfHintController.getHinter());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index fec4c16992a5..a472f79c98e6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -67,6 +67,7 @@ import com.android.wm.shell.dagger.pip.PipModule;
import com.android.wm.shell.desktopmode.CloseDesktopTaskTransitionHandler;
import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopActivityOrientationChangeHandler;
+import com.android.wm.shell.desktopmode.DesktopDisplayEventHandler;
import com.android.wm.shell.desktopmode.DesktopImmersiveController;
import com.android.wm.shell.desktopmode.DesktopMixedTransitionHandler;
import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
@@ -778,7 +779,8 @@ public abstract class WMShellModule {
ShellTaskOrganizer shellTaskOrganizer,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
ReturnToDragStartAnimator returnToDragStartAnimator,
- @DynamicOverride DesktopRepository desktopRepository) {
+ @DynamicOverride DesktopRepository desktopRepository,
+ DesktopModeEventLogger desktopModeEventLogger) {
return new DesktopTilingDecorViewModel(
context,
displayController,
@@ -788,7 +790,8 @@ public abstract class WMShellModule {
shellTaskOrganizer,
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
- desktopRepository
+ desktopRepository,
+ desktopModeEventLogger
);
}
@@ -1016,6 +1019,30 @@ public abstract class WMShellModule {
@WMSingleton
@Provides
+ static Optional<DesktopDisplayEventHandler> provideDesktopDisplayEventHandler(
+ Context context,
+ ShellInit shellInit,
+ Transitions transitions,
+ DisplayController displayController,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
+ IWindowManager windowManager
+ ) {
+ if (!DesktopModeStatus.canEnterDesktopMode(context)
+ || !Flags.enableDisplayWindowingModeSwitching()) {
+ return Optional.empty();
+ }
+ return Optional.of(
+ new DesktopDisplayEventHandler(
+ context,
+ shellInit,
+ transitions,
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ windowManager));
+ }
+
+ @WMSingleton
+ @Provides
static AppHandleEducationDatastoreRepository provideAppHandleEducationDatastoreRepository(
Context context) {
return new AppHandleEducationDatastoreRepository(context);
@@ -1180,7 +1207,8 @@ public abstract class WMShellModule {
@Provides
static Object provideIndependentShellComponentsToCreate(
DragAndDropController dragAndDropController,
- Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional) {
+ Optional<DesktopTasksTransitionObserver> desktopTasksTransitionObserverOptional,
+ Optional<DesktopDisplayEventHandler> desktopDisplayEventHandler) {
return new Object();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
new file mode 100644
index 000000000000..ba383fac8b47
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandler.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.content.Context
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+
+/** Handles display events in desktop mode */
+class DesktopDisplayEventHandler(
+ private val context: Context,
+ shellInit: ShellInit,
+ private val transitions: Transitions,
+ private val displayController: DisplayController,
+ private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
+ private val windowManager: IWindowManager,
+) : OnDisplaysChangedListener {
+
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ private fun onInit() {
+ displayController.addDisplayWindowListener(this)
+ }
+
+ override fun onDisplayAdded(displayId: Int) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return
+ }
+ refreshDisplayWindowingMode()
+ }
+
+ override fun onDisplayRemoved(displayId: Int) {
+ if (displayId == DEFAULT_DISPLAY) {
+ return
+ }
+ refreshDisplayWindowingMode()
+ }
+
+ private fun refreshDisplayWindowingMode() {
+ // TODO: b/375319538 - Replace the check with a DisplayManager API once it's available.
+ val isExtendedDisplayEnabled = 0 != Settings.Global.getInt(
+ context.contentResolver, DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS, 0
+ )
+ if (!isExtendedDisplayEnabled) {
+ // No action needed in mirror or projected mode.
+ return
+ }
+
+ val hasNonDefaultDisplay = rootTaskDisplayAreaOrganizer.getDisplayIds()
+ .any { displayId -> displayId != DEFAULT_DISPLAY }
+ val targetDisplayWindowingMode =
+ if (hasNonDefaultDisplay) {
+ WINDOWING_MODE_FREEFORM
+ } else {
+ // Use the default display windowing mode when no non-default display.
+ windowManager.getWindowingMode(DEFAULT_DISPLAY)
+ }
+ val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)
+ requireNotNull(tdaInfo) { "DisplayAreaInfo of DEFAULT_DISPLAY must be non-null." }
+ if (tdaInfo.configuration.windowConfiguration.windowingMode == targetDisplayWindowingMode) {
+ // Already in the target mode.
+ return
+ }
+
+ val wct = WindowContainerTransaction()
+ wct.setWindowingMode(tdaInfo.token, targetDisplayWindowingMode)
+ transitions.startTransition(TRANSIT_CHANGE, wct, /* handler= */ null)
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
index 48bb2a8b4a74..cefcb757690f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandler.kt
@@ -205,6 +205,11 @@ class DesktopMixedTransitionHandler(
finishTransaction: SurfaceControl.Transaction,
finishCallback: TransitionFinishCallback,
): Boolean {
+ val launchChange = findDesktopTaskChange(info, pending.launchingTask)
+ if (launchChange == null) {
+ logV("No launch Change, returning")
+ return false
+ }
// Check if there's also an immersive change during this launch.
val immersiveExitChange = pending.exitingImmersiveTask?.let { exitingTask ->
findDesktopTaskChange(info, exitingTask)
@@ -212,8 +217,6 @@ class DesktopMixedTransitionHandler(
val minimizeChange = pending.minimizingTask?.let { minimizingTask ->
findDesktopTaskChange(info, minimizingTask)
}
- val launchChange = findDesktopTaskChange(info, pending.launchingTask)
- ?: error("Should have pending launching task change")
var subAnimationCount = -1
var combinedWct: WindowContainerTransaction? = null
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index edcc877ef58e..c7cf31081c8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -275,7 +275,7 @@ private fun TaskInfo.hasPortraitTopActivity(): Boolean {
}
// Then check if the activity is portrait when letterboxed
- appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxed
+ appCompatTaskInfo.isTopActivityLetterboxed -> appCompatTaskInfo.isTopActivityPillarboxShaped
// Then check if the activity is portrait
appBounds != null -> appBounds.height() > appBounds.width()
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 75f8839692a2..162879c97a16 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
@@ -554,19 +554,6 @@ class DesktopTasksController(
}
}
- /** Move a desktop app to split screen. */
- fun moveToSplit(task: RunningTaskInfo) {
- logV( "moveToSplit taskId=%s", task.taskId)
- desktopTilingDecorViewModel.removeTaskIfTiled(task.displayId, task.taskId)
- val wct = WindowContainerTransaction()
- wct.setBounds(task.token, Rect())
- // Rather than set windowing mode to multi-window at task level, set it to
- // undefined and inherit from split stage.
- wct.setWindowingMode(task.token, WINDOWING_MODE_UNDEFINED)
-
- transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
- }
-
private fun exitSplitIfApplicable(wct: WindowContainerTransaction, taskInfo: RunningTaskInfo) {
if (splitScreenController.isTaskInSplitScreen(taskInfo.taskId)) {
splitScreenController.prepareExitSplitScreen(
@@ -2050,6 +2037,18 @@ class DesktopTasksController(
}
/**
+ * Cancel the drag-to-desktop transition.
+ *
+ * @param taskInfo the task being dragged.
+ */
+ fun onDragPositioningCancelThroughStatusBar(
+ taskInfo: RunningTaskInfo,
+ ) {
+ interactionJankMonitor.cancel(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
+ cancelDragToDesktop(taskInfo)
+ }
+
+ /**
* Perform checks required when drag ends under status bar area.
*
* @param taskInfo the task being dragged.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 96719fa2301a..941115083717 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -21,6 +21,7 @@ import android.animation.RectEvaluator
import android.animation.ValueAnimator
import android.graphics.Rect
import android.os.IBinder
+import android.view.Choreographer
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.window.TransitionInfo
@@ -126,6 +127,7 @@ class ToggleResizeDesktopTaskTransitionHandler(
tx.setPosition(leash, rect.left.toFloat(), rect.top.toFloat())
.setWindowCrop(leash, rect.width(), rect.height())
.show(leash)
+ .setFrameTimeline(Choreographer.getInstance().getVsyncId())
onTaskResizeAnimationListener.onBoundsChange(taskId, tx, rect)
}
start()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index f8d2011d0934..3ad9950fdd52 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -26,7 +26,6 @@ import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_SLEEP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -194,7 +193,7 @@ public class KeyguardTransitionHandler
// Occlude/unocclude animations are only played if the keyguard is locked.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0) {
- if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+ if (isKeyguardOccluding(info)) {
if (hasOpeningDream(info)) {
return startAnimation(mOccludeByDreamTransition, "occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
@@ -202,7 +201,7 @@ public class KeyguardTransitionHandler
return startAnimation(mOccludeTransition, "occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
+ } else if (isKeyguardUnoccluding(info)) {
return startAnimation(mUnoccludeTransition, "unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
@@ -325,6 +324,28 @@ public class KeyguardTransitionHandler
return false;
}
+ private static boolean isKeyguardOccluding(@NonNull TransitionInfo info) {
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+ && change.getMode() == TRANSIT_TO_FRONT) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isKeyguardUnoccluding(@NonNull TransitionInfo info) {
+ for (int i = 0; i < info.getChanges().size(); i++) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (change.hasFlags(TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA)
+ && change.getMode() == TRANSIT_TO_BACK) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void finishAnimationImmediately(IBinder transition, StartedTransition playing) {
final IBinder fakeTransition = new Binder();
final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
index 24077a35d41c..026482004d51 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/PipSurfaceTransactionHelper.java
@@ -24,7 +24,6 @@ import android.view.Choreographer;
import android.view.SurfaceControl;
import com.android.wm.shell.R;
-import com.android.wm.shell.transition.Transitions;
/**
* Abstracts the common operations on {@link SurfaceControl.Transaction} for PiP transition.
@@ -180,8 +179,7 @@ public class PipSurfaceTransactionHelper {
// destination are different.
final float scale = srcW <= srcH ? (float) destW / srcW : (float) destH / srcH;
final Rect crop = mTmpDestinationRect;
- crop.set(0, 0, Transitions.SHELL_TRANSITIONS_ROTATION ? destH
- : destW, Transitions.SHELL_TRANSITIONS_ROTATION ? destW : destH);
+ crop.set(0, 0, destW, destH);
// Inverse scale for crop to fit in screen coordinates.
crop.scale(1 / scale);
crop.offset(insets.left, insets.top);
@@ -200,8 +198,8 @@ public class PipSurfaceTransactionHelper {
}
}
mTmpTransform.setScale(scale, scale);
- mTmpTransform.postRotate(degrees);
mTmpTransform.postTranslate(positionX, positionY);
+ mTmpTransform.postRotate(degrees);
tx.setMatrix(leash, mTmpTransform, mTmpFloat9).setCrop(leash, crop);
return this;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
index e5137582822d..eb33ff4c1c8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java
@@ -20,6 +20,7 @@ import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
@@ -34,6 +35,7 @@ import android.window.TransitionInfo;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipUtils;
@@ -45,8 +47,7 @@ import com.android.wm.shell.shared.pip.PipContentOverlay;
/**
* Animator that handles bounds animations for entering PIP.
*/
-public class PipEnterAnimator extends ValueAnimator
- implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener {
+public class PipEnterAnimator extends ValueAnimator {
@NonNull private final SurfaceControl mLeash;
private final SurfaceControl.Transaction mStartTransaction;
private final SurfaceControl.Transaction mFinishTransaction;
@@ -56,49 +57,82 @@ public class PipEnterAnimator extends ValueAnimator
private final RectEvaluator mRectEvaluator;
private final Rect mEndBounds = new Rect();
- @Nullable private final Rect mSourceRectHint;
private final @Surface.Rotation int mRotation;
@Nullable private Runnable mAnimationStartCallback;
@Nullable private Runnable mAnimationEndCallback;
- private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
+ private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory
mSurfaceControlTransactionFactory;
Matrix mTransformTensor = new Matrix();
final float[] mMatrixTmp = new float[9];
@Nullable private PipContentOverlay mContentOverlay;
+ private PipAppIconOverlaySupplier mPipAppIconOverlaySupplier;
// Internal state representing initial transform - cached to avoid recalculation.
private final PointF mInitScale = new PointF();
private final PointF mInitPos = new PointF();
private final Rect mInitCrop = new Rect();
- private final PointF mInitActivityScale = new PointF();
- private final PointF mInitActivityPos = new PointF();
+
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ if (mAnimationStartCallback != null) {
+ mAnimationStartCallback.run();
+ }
+ if (mStartTransaction != null) {
+ onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
+ mStartTransaction.apply();
+ }
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mFinishTransaction != null) {
+ onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
+ }
+ if (mAnimationEndCallback != null) {
+ mAnimationEndCallback.run();
+ }
+ }
+ };
+
+ private final AnimatorUpdateListener mAnimatorUpdateListener = new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(@NonNull ValueAnimator animation) {
+ final SurfaceControl.Transaction tx =
+ mSurfaceControlTransactionFactory.getTransaction();
+ final float fraction = getAnimatedFraction();
+ onEnterAnimationUpdate(fraction, tx);
+ tx.apply();
+ }
+ };
public PipEnterAnimator(Context context,
@NonNull SurfaceControl leash,
SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction,
@NonNull Rect endBounds,
- @Nullable Rect sourceRectHint,
@Surface.Rotation int rotation) {
mLeash = leash;
mStartTransaction = startTransaction;
mFinishTransaction = finishTransaction;
mRectEvaluator = new RectEvaluator(mAnimatedRect);
mEndBounds.set(endBounds);
- mSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint) : null;
mRotation = rotation;
mSurfaceControlTransactionFactory =
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
+ mPipAppIconOverlaySupplier = this::getAppIconOverlay;
final int enterAnimationDuration = context.getResources()
.getInteger(R.integer.config_pipEnterAnimationDuration);
setDuration(enterAnimationDuration);
setFloatValues(0f, 1f);
setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- addListener(this);
- addUpdateListener(this);
+ addListener(mAnimatorListener);
+ addUpdateListener(mAnimatorUpdateListener);
}
public void setAnimationStartCallback(@NonNull Runnable runnable) {
@@ -109,35 +143,6 @@ public class PipEnterAnimator extends ValueAnimator
mAnimationEndCallback = runnable;
}
- @Override
- public void onAnimationStart(@NonNull Animator animation) {
- if (mAnimationStartCallback != null) {
- mAnimationStartCallback.run();
- }
- if (mStartTransaction != null) {
- onEnterAnimationUpdate(0f /* fraction */, mStartTransaction);
- mStartTransaction.apply();
- }
- }
-
- @Override
- public void onAnimationEnd(@NonNull Animator animation) {
- if (mFinishTransaction != null) {
- onEnterAnimationUpdate(1f /* fraction */, mFinishTransaction);
- }
- if (mAnimationEndCallback != null) {
- mAnimationEndCallback.run();
- }
- }
-
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator animation) {
- final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
- final float fraction = getAnimatedFraction();
- onEnterAnimationUpdate(fraction, tx);
- tx.apply();
- }
-
/**
* Updates the transaction to reflect the state of PiP leash at a certain fraction during enter.
*
@@ -177,14 +182,6 @@ public class PipEnterAnimator extends ValueAnimator
}
}
- // no-ops
-
- @Override
- public void onAnimationCancel(@NonNull Animator animation) {}
-
- @Override
- public void onAnimationRepeat(@NonNull Animator animation) {}
-
/**
* Caches the initial transform relevant values for the bounds enter animation.
*
@@ -201,18 +198,13 @@ public class PipEnterAnimator extends ValueAnimator
*/
public void setAppIconContentOverlay(Context context, Rect appBounds, Rect destinationBounds,
ActivityInfo activityInfo, int appIconSizePx) {
- reattachAppIconOverlay(
- new PipAppIconOverlay(context, appBounds, destinationBounds,
- new IconProvider(context).getIcon(activityInfo), appIconSizePx));
- }
-
- private void reattachAppIconOverlay(PipAppIconOverlay overlay) {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
if (mContentOverlay != null) {
mContentOverlay.detach(tx);
}
- mContentOverlay = overlay;
+ mContentOverlay = mPipAppIconOverlaySupplier.get(context, appBounds, destinationBounds,
+ activityInfo, appIconSizePx);
mContentOverlay.attach(tx, mLeash);
}
@@ -229,6 +221,13 @@ public class PipEnterAnimator extends ValueAnimator
mContentOverlay = null;
}
+ private PipAppIconOverlay getAppIconOverlay(
+ Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize) {
+ return new PipAppIconOverlay(context, appBounds, destinationBounds,
+ new IconProvider(context).getIcon(activityInfo), iconSize);
+ }
+
/**
* @return the app icon overlay leash; null if no overlay is attached.
*/
@@ -239,4 +238,21 @@ public class PipEnterAnimator extends ValueAnimator
}
return mContentOverlay.getLeash();
}
+
+ @VisibleForTesting
+ void setSurfaceControlTransactionFactory(
+ @NonNull PipSurfaceTransactionHelper.SurfaceControlTransactionFactory factory) {
+ mSurfaceControlTransactionFactory = factory;
+ }
+
+ @VisibleForTesting
+ interface PipAppIconOverlaySupplier {
+ PipAppIconOverlay get(Context context, Rect appBounds, Rect destinationBounds,
+ ActivityInfo activityInfo, int iconSize);
+ }
+
+ @VisibleForTesting
+ void setPipAppIconOverlaySupplier(@NonNull PipAppIconOverlaySupplier supplier) {
+ mPipAppIconOverlaySupplier = supplier;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
index 3f9b0c30e314..fb1aba399585 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.pip2.animation;
+import static android.view.Surface.ROTATION_90;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.RectEvaluator;
@@ -73,6 +75,7 @@ public class PipExpandAnimator extends ValueAnimator {
mAnimationStartCallback.run();
}
if (mStartTransaction != null) {
+ onExpandAnimationUpdate(mStartTransaction, 0f);
mStartTransaction.apply();
}
}
@@ -81,13 +84,7 @@ public class PipExpandAnimator extends ValueAnimator {
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (mFinishTransaction != null) {
- // finishTransaction might override some state (eg. corner radii) so we want to
- // manually set the state to the end of the animation
- mPipSurfaceTransactionHelper.scaleAndCrop(mFinishTransaction, mLeash,
- mSourceRectHint, mBaseBounds, mAnimatedRect, getInsets(1f),
- false /* isInPipDirection */, 1f)
- .round(mFinishTransaction, mLeash, false /* applyCornerRadius */)
- .shadow(mFinishTransaction, mLeash, false /* applyCornerRadius */);
+ onExpandAnimationUpdate(mFinishTransaction, 1f);
}
if (mAnimationEndCallback != null) {
mAnimationEndCallback.run();
@@ -102,14 +99,7 @@ public class PipExpandAnimator extends ValueAnimator {
final SurfaceControl.Transaction tx =
mSurfaceControlTransactionFactory.getTransaction();
final float fraction = getAnimatedFraction();
-
- // TODO (b/350801661): implement fixed rotation
- Rect insets = getInsets(fraction);
- mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint,
- mBaseBounds, mAnimatedRect,
- insets, false /* isInPipDirection */, fraction)
- .round(tx, mLeash, false /* applyCornerRadius */)
- .shadow(tx, mLeash, false /* applyCornerRadius */);
+ onExpandAnimationUpdate(tx, fraction);
tx.apply();
}
};
@@ -167,6 +157,32 @@ public class PipExpandAnimator extends ValueAnimator {
mAnimationEndCallback = runnable;
}
+ private void onExpandAnimationUpdate(SurfaceControl.Transaction tx, float fraction) {
+ Rect insets = getInsets(fraction);
+ if (mRotation == Surface.ROTATION_0) {
+ mPipSurfaceTransactionHelper.scaleAndCrop(tx, mLeash, mSourceRectHint, mBaseBounds,
+ mAnimatedRect, insets, false /* isInPipDirection */, fraction);
+ } else {
+ // Fixed rotation case.
+ Rect start = mStartBounds;
+ Rect end = mEndBounds;
+ float degrees, x, y;
+ x = fraction * (end.left - start.left) + start.left;
+ y = fraction * (end.top - start.top) + start.top;
+
+ if (mRotation == ROTATION_90) {
+ degrees = 90 * fraction;
+ } else {
+ degrees = -90 * fraction;
+ }
+ mPipSurfaceTransactionHelper.rotateAndScaleWithCrop(tx, mLeash, mBaseBounds,
+ mAnimatedRect, insets, degrees, x, y,
+ true /* isExpanding */, mRotation == ROTATION_90);
+ }
+ mPipSurfaceTransactionHelper.round(tx, mLeash, false /* applyCornerRadius */)
+ .shadow(tx, mLeash, false /* applyShadowRadius */);
+ }
+
private Rect getInsets(float fraction) {
final Rect startInsets = mSourceRectHintInsets;
final Rect endInsets = mZeroInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
index 012dabbbb9f8..4558a9f141c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipResizeAnimator.java
@@ -30,6 +30,7 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.shared.animation.Interpolators;
/**
* Animator that handles any resize related animation for PIP.
@@ -128,6 +129,7 @@ public class PipResizeAnimator extends ValueAnimator {
mRectEvaluator = new RectEvaluator(mAnimatedRect);
setObjectValues(startBounds, endBounds);
+ setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
addListener(mAnimatorListener);
addUpdateListener(mAnimatorUpdateListener);
setEvaluator(mRectEvaluator);
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 9a93371621c9..d3f537b8f904 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
@@ -376,18 +376,20 @@ public class PipController implements ConfigurationChangeListener,
private void setLauncherKeepClearAreaHeight(boolean visible, int height) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"setLauncherKeepClearAreaHeight: visible=%b, height=%d", visible, height);
- if (visible) {
- Rect rect = new Rect(
- 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
- mPipDisplayLayoutState.getDisplayBounds().right,
- mPipDisplayLayoutState.getDisplayBounds().bottom);
- mPipBoundsState.setNamedUnrestrictedKeepClearArea(
- PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
- } else {
- mPipBoundsState.setNamedUnrestrictedKeepClearArea(
- PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
- }
- mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+ mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
+ if (visible) {
+ Rect rect = new Rect(
+ 0, mPipDisplayLayoutState.getDisplayBounds().bottom - height,
+ mPipDisplayLayoutState.getDisplayBounds().right,
+ mPipDisplayLayoutState.getDisplayBounds().bottom);
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, rect);
+ } else {
+ mPipBoundsState.setNamedUnrestrictedKeepClearArea(
+ PipBoundsState.NAMED_KCA_LAUNCHER_SHELF, null);
+ }
+ mPipTouchHandler.onShelfVisibilityChanged(visible, height);
+ });
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
index 2c7584af1f07..1b5eebaa8aa1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTaskListener.java
@@ -29,6 +29,7 @@ import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.protolog.ProtoLog;
import com.android.internal.util.Preconditions;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
@@ -36,6 +37,7 @@ import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.pip2.animation.PipResizeAnimator;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import java.util.ArrayList;
@@ -121,6 +123,9 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
if (mPictureInPictureParams.equals(params)) {
return;
}
+ ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+ "onTaskInfoChanged: %s, state=%s oldParams=%s newParams=%s",
+ taskInfo.topActivity, mPipTransitionState, mPictureInPictureParams, params);
setPictureInPictureParams(params);
float newAspectRatio = mPictureInPictureParams.getAspectRatioFloat();
if (PipUtils.aspectRatioChanged(newAspectRatio, mPipBoundsState.getAspectRatio())) {
@@ -171,6 +176,7 @@ public class PipTaskListener implements ShellTaskOrganizer.TaskListener,
"Leash is null for bounds transition.");
if (mWaitingForAspectRatioChange) {
+ mWaitingForAspectRatioChange = false;
PipResizeAnimator animator = new PipResizeAnimator(mContext,
mPipTransitionState.mPinnedTaskLeash, startTx, finishTx,
destinationBounds,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
index 19d293e829ad..9af1aba51c57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java
@@ -231,17 +231,15 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha
// KCA triggered movement to wait for other transitions (e.g. due to IME changes).
return;
}
- mPipTransitionState.setOnIdlePipTransitionStateRunnable(() -> {
- boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
- || mPipBoundsState.hasUserResizedPip());
- int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
- - mPipBoundsState.getBounds().top;
+ boolean hasUserInteracted = (mPipBoundsState.hasUserMovedPip()
+ || mPipBoundsState.hasUserResizedPip());
+ int delta = mPipBoundsAlgorithm.getEntryDestinationBounds().top
+ - mPipBoundsState.getBounds().top;
- if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
- // If the user hasn't interacted with PiP, we respect the keep clear areas
- mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
- }
- });
+ if (!mIsImeShowing && !hasUserInteracted && delta != 0) {
+ // If the user hasn't interacted with PiP, we respect the keep clear areas
+ mMotionHelper.animateToOffset(mPipBoundsState.getBounds(), delta);
+ }
};
if (PipUtils.isPip2ExperimentEnabled()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index 64d8887ae5cd..3930b423d8a1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -325,9 +325,7 @@ public class PipTransition extends PipTransitionController implements
return false;
}
- SurfaceControl pipLeash = pipChange.getLeash();
- Preconditions.checkNotNull(pipLeash, "Leash is null for swipe-up transition.");
-
+ final SurfaceControl pipLeash = getLeash(pipChange);
final Rect destinationBounds = pipChange.getEndAbsBounds();
final SurfaceControl swipePipToHomeOverlay = mPipTransitionState.getSwipePipToHomeOverlay();
if (swipePipToHomeOverlay != null) {
@@ -349,20 +347,14 @@ public class PipTransition extends PipTransitionController implements
: startRotation - endRotation;
if (delta != ROTATION_0) {
mPipTransitionState.setInFixedRotation(true);
- handleBoundsTypeFixedRotation(pipChange, pipActivityChange, endRotation);
- }
-
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
+ handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
}
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, destinationBounds, sourceRectHint, delta);
+ startTransaction, finishTransaction, destinationBounds, delta);
animator.setEnterStartState(pipChange);
animator.onEnterAnimationUpdate(1.0f /* fraction */, startTransaction);
startTransaction.apply();
@@ -420,20 +412,20 @@ public class PipTransition extends PipTransitionController implements
}
final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- int startRotation = pipChange.getStartRotation();
- int endRotation = fixedRotationChange != null
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = fixedRotationChange != null
? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
: startRotation - endRotation;
if (delta != ROTATION_0) {
mPipTransitionState.setInFixedRotation(true);
- handleBoundsTypeFixedRotation(pipChange, pipActivityChange,
+ handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
fixedRotationChange.getEndFixedRotation());
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
- startTransaction, finishTransaction, endBounds, adjustedSourceRectHint, delta);
+ startTransaction, finishTransaction, endBounds, delta);
if (sourceRectHint == null) {
// update the src-rect-hint in params in place, to set up initial animator transform.
params.getSourceRectHint().set(adjustedSourceRectHint);
@@ -465,7 +457,7 @@ public class PipTransition extends PipTransitionController implements
animator.start();
}
- private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange,
+ private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
TransitionInfo.Change pipActivityChange, int endRotation) {
final Rect endBounds = pipTaskChange.getEndAbsBounds();
final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
@@ -498,6 +490,26 @@ public class PipTransition extends PipTransitionController implements
endBounds.top + activityEndOffset.y);
}
+ private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
+ final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ final int width = endBounds.width();
+ final int height = endBounds.height();
+ final int left = endBounds.left;
+ final int top = endBounds.top;
+ int newTop, newLeft;
+
+ if (endRotation == Surface.ROTATION_90) {
+ newLeft = top;
+ newTop = -(left + width);
+ } else {
+ newLeft = -(height + top);
+ newTop = left;
+ }
+ // Modify the endBounds, rotating and placing them potentially off-screen, so that
+ // as we translate and rotate around the origin, we place them right into the target.
+ endBounds.set(newLeft, newTop, newLeft + height, newTop + width);
+ }
+
private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@@ -550,33 +562,51 @@ public class PipTransition extends PipTransitionController implements
}
}
- // for multi activity, we need to manually set the leash layer
- if (pipChange.getTaskInfo() == null) {
- TransitionInfo.Change parent = getChangeByToken(info, pipChange.getParent());
- if (parent != null) {
- startTransaction.setLayer(parent.getLeash(), Integer.MAX_VALUE - 1);
- }
+ // The parent change if we were in a multi-activity PiP; null if single activity PiP.
+ final TransitionInfo.Change parentBeforePip = pipChange.getTaskInfo() == null
+ ? getChangeByToken(info, pipChange.getParent()) : null;
+ if (parentBeforePip != null) {
+ // For multi activity, we need to manually set the leash layer
+ startTransaction.setLayer(parentBeforePip.getLeash(), Integer.MAX_VALUE - 1);
}
- Rect startBounds = pipChange.getStartAbsBounds();
- Rect endBounds = pipChange.getEndAbsBounds();
- SurfaceControl pipLeash = pipChange.getLeash();
- Preconditions.checkNotNull(pipLeash, "Leash is null for exit transition.");
+ final Rect startBounds = pipChange.getStartAbsBounds();
+ final Rect endBounds = pipChange.getEndAbsBounds();
+ final SurfaceControl pipLeash = getLeash(pipChange);
- Rect sourceRectHint = null;
- if (pipChange.getTaskInfo() != null
- && pipChange.getTaskInfo().pictureInPictureParams != null) {
+ PictureInPictureParams params = null;
+ if (pipChange.getTaskInfo() != null) {
// single activity
- sourceRectHint = pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint();
- } else if (mPipTaskListener.getPictureInPictureParams().hasSourceBoundsHint()) {
+ params = pipChange.getTaskInfo().pictureInPictureParams;
+ } else if (parentBeforePip != null && parentBeforePip.getTaskInfo() != null) {
// multi activity
- sourceRectHint = mPipTaskListener.getPictureInPictureParams().getSourceRectHint();
+ params = parentBeforePip.getTaskInfo().pictureInPictureParams;
+ }
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
+ startBounds);
+
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
+ final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : endRotation - startRotation;
+
+ if (delta != ROTATION_0) {
+ handleExpandFixedRotation(pipChange, endRotation);
}
PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, startBounds, endBounds,
- sourceRectHint, Surface.ROTATION_0);
- animator.setAnimationEndCallback(this::finishTransition);
+ sourceRectHint, delta);
+ animator.setAnimationEndCallback(() -> {
+ if (parentBeforePip != null) {
+ // TODO b/377362511: Animate local leash instead to also handle letterbox case.
+ // For multi-activity, set the crop to be null
+ finishTransaction.setCrop(pipLeash, null);
+ }
+ finishTransition();
+ });
animator.start();
return true;
}
@@ -723,6 +753,13 @@ public class PipTransition extends PipTransitionController implements
}
}
+ @NonNull
+ private SurfaceControl getLeash(TransitionInfo.Change change) {
+ SurfaceControl leash = change.getLeash();
+ Preconditions.checkNotNull(leash, "Leash is null for change=" + change);
+ return leash;
+ }
+
//
// Miscellaneous callbacks and listeners
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
index 799028a5507a..4a301cc0b603 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasks.aidl
@@ -24,7 +24,7 @@ import android.os.Bundle;
import com.android.wm.shell.recents.IRecentsAnimationRunner;
import com.android.wm.shell.recents.IRecentTasksListener;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
/**
* Interface that is exposed to remote callers to fetch recent tasks.
@@ -44,7 +44,7 @@ interface IRecentTasks {
/**
* Gets the set of recent tasks.
*/
- GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
+ GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId) = 3;
/**
* Gets the set of running tasks.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
index 371bdd5c6469..b58f0681c571 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/IRecentTasksListener.aidl
@@ -18,6 +18,8 @@ package com.android.wm.shell.recents;
import android.app.ActivityManager.RunningTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
+
/**
* Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
*/
@@ -44,8 +46,8 @@ oneway interface IRecentTasksListener {
void onRunningTaskChanged(in RunningTaskInfo taskInfo);
/** A task has moved to front. */
- oneway void onTaskMovedToFront(in RunningTaskInfo taskInfo);
+ void onTaskMovedToFront(in GroupedTaskInfo[] visibleTasks);
/** A task info has changed. */
- oneway void onTaskInfoChanged(in RunningTaskInfo taskInfo);
+ void onTaskInfoChanged(in RunningTaskInfo taskInfo);
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index 8c5d1e7e069d..364a087211c5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -19,7 +19,7 @@ package com.android.wm.shell.recents;
import android.annotation.Nullable;
import android.graphics.Color;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import java.util.List;
@@ -35,7 +35,7 @@ public interface RecentTasks {
* Gets the set of recent tasks.
*/
default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
- Consumer<List<GroupedRecentTaskInfo>> callback) {
+ Consumer<List<GroupedTaskInfo>> callback) {
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index faa20159f64a..9911669d2cb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -24,10 +24,12 @@ import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_R
import android.Manifest;
import android.annotation.RequiresPermission;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager;
import android.app.IApplicationThread;
import android.app.KeyguardManager;
import android.app.PendingIntent;
+import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -55,7 +57,7 @@ import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.annotations.ExternalThread;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -379,7 +381,8 @@ public class RecentTasksController implements TaskStackListenerCallback,
return;
}
try {
- mListener.onTaskMovedToFront(taskInfo);
+ GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+ mListener.onTaskMovedToFront(new GroupedTaskInfo[]{ runningTask });
} catch (RemoteException e) {
Slog.w(TAG, "Failed call onTaskMovedToFront", e);
}
@@ -407,27 +410,27 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@VisibleForTesting
- ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
+ ArrayList<GroupedTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is from the most-recent to least-recent order
- final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+ final List<RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
maxNum, flags, userId);
// Make a mapping of task id -> task info
- final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
+ final SparseArray<TaskInfo> rawMapping = new SparseArray<>();
for (int i = 0; i < rawList.size(); i++) {
- final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+ final TaskInfo taskInfo = rawList.get(i);
rawMapping.put(taskInfo.taskId, taskInfo);
}
- ArrayList<ActivityManager.RecentTaskInfo> freeformTasks = new ArrayList<>();
+ ArrayList<TaskInfo> freeformTasks = new ArrayList<>();
Set<Integer> minimizedFreeformTasks = new HashSet<>();
int mostRecentFreeformTaskIndex = Integer.MAX_VALUE;
// Pull out the pairs as we iterate back in the list
- ArrayList<GroupedRecentTaskInfo> recentTasks = new ArrayList<>();
+ ArrayList<GroupedTaskInfo> recentTasks = new ArrayList<>();
for (int i = 0; i < rawList.size(); i++) {
- final ActivityManager.RecentTaskInfo taskInfo = rawList.get(i);
+ final RecentTaskInfo taskInfo = rawList.get(i);
if (!rawMapping.contains(taskInfo.taskId)) {
// If it's not in the mapping, then it was already paired with another task
continue;
@@ -460,20 +463,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
final int pairedTaskId = mSplitTasks.get(taskInfo.taskId, INVALID_TASK_ID);
if (pairedTaskId != INVALID_TASK_ID && rawMapping.contains(
pairedTaskId)) {
- final ActivityManager.RecentTaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
+ final TaskInfo pairedTaskInfo = rawMapping.get(pairedTaskId);
rawMapping.remove(pairedTaskId);
- recentTasks.add(GroupedRecentTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
+ recentTasks.add(GroupedTaskInfo.forSplitTasks(taskInfo, pairedTaskInfo,
mTaskSplitBoundsMap.get(pairedTaskId)));
} else {
- recentTasks.add(GroupedRecentTaskInfo.forSingleTask(taskInfo));
+ recentTasks.add(GroupedTaskInfo.forFullscreenTasks(taskInfo));
}
}
// Add a special entry for freeform tasks
if (!freeformTasks.isEmpty()) {
recentTasks.add(mostRecentFreeformTaskIndex,
- GroupedRecentTaskInfo.forFreeformTasks(
- freeformTasks.toArray(new ActivityManager.RecentTaskInfo[0]),
+ GroupedTaskInfo.forFreeformTasks(
+ freeformTasks,
minimizedFreeformTasks));
}
@@ -514,16 +517,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
* {@param ignoreTaskToken} if it is non-null.
*/
@Nullable
- public ActivityManager.RecentTaskInfo findTaskInBackground(ComponentName componentName,
+ public RecentTaskInfo findTaskInBackground(ComponentName componentName,
int userId, @Nullable WindowContainerToken ignoreTaskToken) {
if (componentName == null) {
return null;
}
- List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+ List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
ActivityManager.getCurrentUser());
for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RecentTaskInfo task = tasks.get(i);
+ final RecentTaskInfo task = tasks.get(i);
if (task.isVisible) {
continue;
}
@@ -541,12 +544,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
* Find the background task that match the given taskId.
*/
@Nullable
- public ActivityManager.RecentTaskInfo findTaskInBackground(int taskId) {
- List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+ public RecentTaskInfo findTaskInBackground(int taskId) {
+ List<RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
ActivityManager.getCurrentUser());
for (int i = 0; i < tasks.size(); i++) {
- final ActivityManager.RecentTaskInfo task = tasks.get(i);
+ final RecentTaskInfo task = tasks.get(i);
if (task.isVisible) {
continue;
}
@@ -570,7 +573,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
pw.println(prefix + TAG);
pw.println(prefix + " mListener=" + mListener);
pw.println(prefix + "Tasks:");
- ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
+ ArrayList<GroupedTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
for (int i = 0; i < recentTasks.size(); i++) {
pw.println(innerPrefix + recentTasks.get(i));
@@ -584,9 +587,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
private class RecentTasksImpl implements RecentTasks {
@Override
public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
- Consumer<List<GroupedRecentTaskInfo>> callback) {
+ Consumer<List<GroupedTaskInfo>> callback) {
mMainExecutor.execute(() -> {
- List<GroupedRecentTaskInfo> tasks =
+ List<GroupedTaskInfo> tasks =
RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
executor.execute(() -> callback.accept(tasks));
});
@@ -650,7 +653,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo) {
+ public void onTaskMovedToFront(GroupedTaskInfo[] taskInfo) {
mListener.call(l -> l.onTaskMovedToFront(taskInfo));
}
@@ -692,17 +695,20 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@Override
- public GroupedRecentTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
+ public GroupedTaskInfo[] getRecentTasks(int maxNum, int flags, int userId)
throws RemoteException {
if (mController == null) {
// The controller is already invalidated -- just return an empty task list for now
- return new GroupedRecentTaskInfo[0];
+ return new GroupedTaskInfo[0];
}
- final GroupedRecentTaskInfo[][] out = new GroupedRecentTaskInfo[][]{null};
+ final GroupedTaskInfo[][] out = new GroupedTaskInfo[][]{null};
executeRemoteCallWithTaskPermission(mController, "getRecentTasks",
- (controller) -> out[0] = controller.getRecentTasks(maxNum, flags, userId)
- .toArray(new GroupedRecentTaskInfo[0]),
+ (controller) -> {
+ List<GroupedTaskInfo> tasks = controller.getRecentTasks(
+ maxNum, flags, userId);
+ out[0] = tasks.toArray(new GroupedTaskInfo[0]);
+ },
true /* blocking */);
return out[0];
}
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 6d4d4b410be8..40065b9287a6 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
@@ -544,10 +544,10 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler,
.findFirst()
.get();
final RemoteAnimationTarget openingTarget = TransitionUtil.newSyntheticTarget(
- homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_OPEN,
+ homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_OPEN,
0, true /* isTranslucent */);
final RemoteAnimationTarget closingTarget = TransitionUtil.newSyntheticTarget(
- homeTask, mShellTaskOrganizer.getHomeTaskOverlayContainer(), TRANSIT_CLOSE,
+ homeTask, mShellTaskOrganizer.getHomeTaskSurface(), TRANSIT_CLOSE,
0, true /* isTranslucent */);
final ArrayList<RemoteAnimationTarget> apps = new ArrayList<>();
apps.add(openingTarget);
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 b33f3e98f350..4407e5b3106f 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
@@ -245,9 +245,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
- mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested;
+ mVisible = isStageVisible();
mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */,
- mVisible);
+ taskInfo.isVisible && taskInfo.isVisibleRequested);
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
@@ -293,6 +293,19 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
t.reparent(sc, findTaskSurface(taskId));
}
+ /**
+ * Checks against all children task info and return true if any are marked as visible.
+ */
+ private boolean isStageVisible() {
+ for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
+ if (mChildrenTaskInfo.valueAt(i).isVisible
+ && mChildrenTaskInfo.valueAt(i).isVisibleRequested) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private SurfaceControl findTaskSurface(int taskId) {
if (mRootTaskInfo.taskId == taskId) {
return mRootLeash;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e1683f3a903c..17e3dd2fc68e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -41,7 +41,6 @@ import static com.android.wm.shell.shared.desktopmode.ManageWindowsViewContainer
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -49,6 +48,7 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityTaskManager;
import android.app.IActivityManager;
import android.app.IActivityTaskManager;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.graphics.Point;
@@ -122,8 +122,6 @@ import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource;
import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.splitscreen.SplitScreen;
-import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.sysui.KeyguardChangeListener;
import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -446,17 +444,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
@Override
public void setSplitScreenController(SplitScreenController splitScreenController) {
mSplitScreenController = splitScreenController;
- mSplitScreenController.registerSplitScreenListener(new SplitScreen.SplitScreenListener() {
- @Override
- public void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {
- if (visible && stage != STAGE_TYPE_UNDEFINED) {
- DesktopModeWindowDecoration decor = mWindowDecorByTaskId.get(taskId);
- if (decor != null && DesktopModeStatus.canEnterDesktopMode(mContext)) {
- mDesktopTasksController.moveToSplit(decor.mTaskInfo);
- }
- }
- }
- });
}
@Override
@@ -1290,6 +1277,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
break;
}
+ case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mTransitionDragActive) {
final DesktopModeVisualIndicator.DragStartState dragStartState =
@@ -1304,32 +1292,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
// Though this isn't a hover event, we need to update handle's hover state
// as it likely will change.
relevantDecor.updateHoverAndPressStatus(ev);
- DesktopModeVisualIndicator.IndicatorType resultType =
- mDesktopTasksController.onDragPositioningEndThroughStatusBar(
- new PointF(ev.getRawX(), ev.getRawY()),
- relevantDecor.mTaskInfo,
- relevantDecor.mTaskSurface);
- // If we are entering split select, handle will no longer be visible and
- // should not be receiving any input.
- if (resultType == TO_SPLIT_LEFT_INDICATOR
- || resultType == TO_SPLIT_RIGHT_INDICATOR) {
- relevantDecor.disposeStatusBarInputLayer();
- // We should also dispose the other split task's input layer if
- // applicable.
- final int splitPosition = mSplitScreenController
- .getSplitPosition(relevantDecor.mTaskInfo.taskId);
- if (splitPosition != SPLIT_POSITION_UNDEFINED) {
- final int oppositePosition =
- splitPosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT
- : SPLIT_POSITION_TOP_OR_LEFT;
- final RunningTaskInfo oppositeTaskInfo =
- mSplitScreenController.getTaskInfo(oppositePosition);
- if (oppositeTaskInfo != null) {
- mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
- .disposeStatusBarInputLayer();
- }
- }
+ if (ev.getActionMasked() == ACTION_CANCEL) {
+ mDesktopTasksController.onDragPositioningCancelThroughStatusBar(
+ relevantDecor.mTaskInfo);
+ } else {
+ endDragToDesktop(ev, relevantDecor);
}
mMoveToDesktopAnimator = null;
return;
@@ -1378,10 +1345,35 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
}
break;
}
+ }
+ }
- case MotionEvent.ACTION_CANCEL: {
- mTransitionDragActive = false;
- mMoveToDesktopAnimator = null;
+ private void endDragToDesktop(MotionEvent ev, DesktopModeWindowDecoration relevantDecor) {
+ DesktopModeVisualIndicator.IndicatorType resultType =
+ mDesktopTasksController.onDragPositioningEndThroughStatusBar(
+ new PointF(ev.getRawX(), ev.getRawY()),
+ relevantDecor.mTaskInfo,
+ relevantDecor.mTaskSurface);
+ // If we are entering split select, handle will no longer be visible and
+ // should not be receiving any input.
+ if (resultType == TO_SPLIT_LEFT_INDICATOR
+ || resultType == TO_SPLIT_RIGHT_INDICATOR) {
+ relevantDecor.disposeStatusBarInputLayer();
+ // We should also dispose the other split task's input layer if
+ // applicable.
+ final int splitPosition = mSplitScreenController
+ .getSplitPosition(relevantDecor.mTaskInfo.taskId);
+ if (splitPosition != SPLIT_POSITION_UNDEFINED) {
+ final int oppositePosition =
+ splitPosition == SPLIT_POSITION_TOP_OR_LEFT
+ ? SPLIT_POSITION_BOTTOM_OR_RIGHT
+ : SPLIT_POSITION_TOP_OR_LEFT;
+ final RunningTaskInfo oppositeTaskInfo =
+ mSplitScreenController.getTaskInfo(oppositePosition);
+ if (oppositeTaskInfo != null) {
+ mWindowDecorByTaskId.get(oppositeTaskInfo.taskId)
+ .disposeStatusBarInputLayer();
+ }
}
}
}
@@ -1481,6 +1473,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& isTopActivityExemptFromDesktopWindowing(mContext, taskInfo)) {
return false;
}
+ if (isPartOfDefaultHomePackage(taskInfo)) {
+ return false;
+ }
return DesktopModeStatus.canEnterDesktopMode(mContext)
&& !DesktopWallpaperActivity.isWallpaperTask(taskInfo)
&& taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED
@@ -1488,6 +1483,14 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel,
&& !taskInfo.configuration.windowConfiguration.isAlwaysOnTop();
}
+ private boolean isPartOfDefaultHomePackage(RunningTaskInfo taskInfo) {
+ final ComponentName currentDefaultHome =
+ mContext.getPackageManager().getHomeActivities(new ArrayList<>());
+ return currentDefaultHome != null && taskInfo.baseActivity != null
+ && currentDefaultHome.getPackageName()
+ .equals(taskInfo.baseActivity.getPackageName());
+ }
+
private void createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo,
SurfaceControl taskSurface,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
index e43c3a613157..61963cde2d06 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModel.kt
@@ -30,6 +30,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayChangeController
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -48,6 +49,7 @@ class DesktopTilingDecorViewModel(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val taskRepository: DesktopRepository,
+ private val desktopModeEventLogger: DesktopModeEventLogger,
) : DisplayChangeController.OnDisplayChangingListener {
@VisibleForTesting
var tilingTransitionHandlerByDisplayId = SparseArray<DesktopTilingWindowDecoration>()
@@ -80,6 +82,7 @@ class DesktopTilingDecorViewModel(
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
taskRepository,
+ desktopModeEventLogger,
)
tilingTransitionHandlerByDisplayId.put(displayId, newHandler)
newHandler
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
index 209eb5e501b2..6cdc517c9cb7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDividerWindowManager.kt
@@ -23,6 +23,7 @@ import android.graphics.Rect
import android.graphics.Region
import android.os.Binder
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.RoundedCorner
import android.view.SurfaceControl
import android.view.SurfaceControlViewHost
@@ -39,6 +40,7 @@ import android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER
import android.view.WindowlessWindowManager
import com.android.wm.shell.R
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import java.util.function.Supplier
/**
@@ -141,8 +143,9 @@ class DesktopTilingDividerWindowManager(
t.setRelativeLayer(leash, relativeLeash, 1)
}
- override fun onDividerMoveStart(pos: Int) {
+ override fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent) {
setSlippery(false)
+ transitionHandler.onDividerHandleDragStart(motionEvent)
}
/**
@@ -161,13 +164,13 @@ class DesktopTilingDividerWindowManager(
* Notifies the transition handler of tiling operations ending, which might result in resizing
* WindowContainerTransactions if the sizes of the tiled tasks changed.
*/
- override fun onDividerMovedEnd(pos: Int) {
+ override fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent) {
setSlippery(true)
val t = transactionSupplier.get()
t.setPosition(leash, pos.toFloat() - maxRoundedCornerRadius, dividerBounds.top.toFloat())
val dividerWidth = dividerBounds.width()
dividerBounds.set(pos, dividerBounds.top, pos + dividerWidth, dividerBounds.bottom)
- transitionHandler.onDividerHandleDragEnd(dividerBounds, t)
+ transitionHandler.onDividerHandleDragEnd(dividerBounds, t, motionEvent)
}
private fun getWindowManagerParams(): WindowManager.LayoutParams {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
index c46767c3a51d..1c593c0362ba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecoration.kt
@@ -25,6 +25,7 @@ import android.graphics.Rect
import android.os.IBinder
import android.os.UserHandle
import android.util.Slog
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.WindowManager.TRANSIT_CHANGE
@@ -44,6 +45,8 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -70,6 +73,7 @@ class DesktopTilingWindowDecoration(
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
private val returnToDragStartAnimator: ReturnToDragStartAnimator,
private val taskRepository: DesktopRepository,
+ private val desktopModeEventLogger: DesktopModeEventLogger,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() },
) :
Transitions.TransitionHandler,
@@ -218,6 +222,25 @@ class DesktopTilingWindowDecoration(
return tilingManager
}
+ fun onDividerHandleDragStart(motionEvent: MotionEvent) {
+ val leftTiledTask = leftTaskResizingHelper ?: return
+ val rightTiledTask = rightTaskResizingHelper ?: return
+
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ leftTiledTask.taskInfo,
+ displayController
+ )
+
+ desktopModeEventLogger.logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ rightTiledTask.taskInfo,
+ displayController
+ )
+ }
+
fun onDividerHandleMoved(dividerBounds: Rect, t: SurfaceControl.Transaction): Boolean {
val leftTiledTask = leftTaskResizingHelper ?: return false
val rightTiledTask = rightTaskResizingHelper ?: return false
@@ -266,10 +289,32 @@ class DesktopTilingWindowDecoration(
return true
}
- fun onDividerHandleDragEnd(dividerBounds: Rect, t: SurfaceControl.Transaction) {
+ fun onDividerHandleDragEnd(
+ dividerBounds: Rect,
+ t: SurfaceControl.Transaction,
+ motionEvent: MotionEvent,
+ ) {
val leftTiledTask = leftTaskResizingHelper ?: return
val rightTiledTask = rightTaskResizingHelper ?: return
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ leftTiledTask.taskInfo,
+ leftTiledTask.newBounds.height(),
+ leftTiledTask.newBounds.width(),
+ displayController
+ )
+
+ desktopModeEventLogger.logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ rightTiledTask.taskInfo,
+ rightTiledTask.newBounds.height(),
+ rightTiledTask.newBounds.width(),
+ displayController
+ )
+
if (leftTiledTask.newBounds == leftTiledTask.bounds) {
leftTiledTask.hideVeil()
rightTiledTask.hideVeil()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
index b3b30ad4c09e..9799d01afc9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/DividerMoveCallback.kt
@@ -15,14 +15,16 @@
*/
package com.android.wm.shell.windowdecor.tiling
+import android.view.MotionEvent
+
/** Divider move callback to whichever entity that handles the moving logic. */
interface DividerMoveCallback {
/** Called on the divider move start gesture. */
- fun onDividerMoveStart(pos: Int)
+ fun onDividerMoveStart(pos: Int, motionEvent: MotionEvent)
/** Called on the divider moved by dragging it. */
fun onDividerMove(pos: Int): Boolean
/** Called on divider move gesture end. */
- fun onDividerMovedEnd(pos: Int)
+ fun onDividerMovedEnd(pos: Int, motionEvent: MotionEvent)
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
index 89229051941c..111e28e450bd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/tiling/TilingDividerView.kt
@@ -206,7 +206,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
when (event.actionMasked) {
MotionEvent.ACTION_DOWN -> {
if (!isWithinHandleRegion(yTouchPosInDivider)) return true
- callback.onDividerMoveStart(touchPos)
+ callback.onDividerMoveStart(touchPos, event)
setTouching()
canResize = true
}
@@ -230,7 +230,7 @@ class TilingDividerView : FrameLayout, View.OnTouchListener, DragDetector.Motion
if (!canResize) return true
if (moving && resized) {
dividerBounds.left = dividerBounds.left + lastAcceptedPos - startPos
- callback.onDividerMovedEnd(dividerBounds.left)
+ callback.onDividerMovedEnd(dividerBounds.left, event)
}
moving = false
canResize = false
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
new file mode 100644
index 000000000000..e16159c0613a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/functional/MinimizeAppWindowsTest.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.functional
+
+import android.platform.test.annotations.Postsubmit
+import com.android.wm.shell.scenarios.MinimizeAppWindows
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+/* Functional test for [MinimizeAppWindows]. */
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+class MinimizeAppWindowsTest : MinimizeAppWindows()
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
new file mode 100644
index 000000000000..b5483634b057
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/MinimizeAppWindows.kt
@@ -0,0 +1,82 @@
+/*
+ * 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.scenarios
+
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+
+/**
+ * Base scenario test for minimizing all the desktop app windows one-by-one by clicking their
+ * minimize buttons.
+ */
+@Ignore("Test Base Class")
+abstract class MinimizeAppWindows
+constructor(private val rotation: Rotation = Rotation.ROTATION_0) {
+ private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ private val tapl = LauncherInstrumentation()
+ private val wmHelper = WindowManagerStateHelper(instrumentation)
+ private val device = UiDevice.getInstance(instrumentation)
+ private val testApp1 = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+ private val testApp2 = DesktopModeAppHelper(NonResizeableAppHelper(instrumentation))
+ private val testApp3 = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+
+ @Rule @JvmField val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, Rotation.ROTATION_0)
+
+ @Before
+ fun setup() {
+ Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+ Assume.assumeTrue(Flags.enableMinimizeButton())
+ tapl.setEnableRotation(true)
+ tapl.setExpectedRotation(rotation.value)
+ ChangeDisplayOrientationRule.setRotation(rotation)
+ testApp1.enterDesktopWithDrag(wmHelper, device)
+ testApp2.launchViaIntent(wmHelper)
+ testApp3.launchViaIntent(wmHelper)
+ }
+
+ @Test
+ open fun minimizeAllAppWindows() {
+ testApp3.minimizeDesktopApp(wmHelper, device)
+ testApp2.minimizeDesktopApp(wmHelper, device)
+ testApp1.minimizeDesktopApp(wmHelper, device)
+ }
+
+ @After
+ fun teardown() {
+ testApp1.exit(wmHelper)
+ testApp2.exit(wmHelper)
+ testApp3.exit(wmHelper)
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
index 266e48482568..2ed7d07ac75e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java
@@ -62,6 +62,7 @@ public class BackProgressAnimatorTest {
@Before
public void setUp() throws Exception {
+ mTargetProgressCalled = new CountDownLatch(1);
mMainThreadHandler = new Handler(Looper.getMainLooper());
final BackMotionEvent backEvent = backMotionEventFrom(0, 0);
mMainThreadHandler.post(
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
index ecaf970ae389..803e5d4442a9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/AppCompatUtilsTest.kt
@@ -43,38 +43,30 @@ class AppCompatUtilsTest : ShellTestCase() {
.apply {
isTopActivityTransparent = true
numActivities = 1
- }))
- assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
- createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = true
- numActivities = 0
+ isTopActivityNoDisplay = false
}))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing_singleTopActivity() {
- assertTrue(isTopActivityExemptFromDesktopWindowing(mContext,
- createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = true
- numActivities = 1
- }))
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_multipleActivities() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityTransparent = false
- numActivities = 1
- }))
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 2
+ isTopActivityNoDisplay = false
+ }))
}
@Test
- fun testIsTopActivityExemptFromDesktopWindowing__topActivityStyleFloating() {
+ fun testIsTopActivityExemptFromDesktopWindowing_topActivityTransparent_notDisplayed() {
assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
createFreeformTask(/* displayId */ 0)
- .apply {
- isTopActivityStyleFloating = true
- }))
+ .apply {
+ isTopActivityTransparent = true
+ numActivities = 1
+ isTopActivityNoDisplay = true
+ }))
}
@Test
@@ -85,6 +77,19 @@ class AppCompatUtilsTest : ShellTestCase() {
createFreeformTask(/* displayId */ 0)
.apply {
baseActivity = baseComponent
+ isTopActivityNoDisplay = false
}))
}
+
+ @Test
+ fun testIsTopActivityExemptFromDesktopWindowing_systemUiTask_notDisplayed() {
+ val systemUIPackageName = context.resources.getString(R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ assertFalse(isTopActivityExemptFromDesktopWindowing(mContext,
+ createFreeformTask(/* displayId */ 0)
+ .apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }))
+ }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
new file mode 100644
index 000000000000..fea82365c1a0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopDisplayEventHandlerTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.desktopmode
+
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.content.ContentResolver
+import android.os.Binder
+import android.provider.Settings
+import android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+import android.testing.AndroidTestingRunner
+import android.view.Display.DEFAULT_DISPLAY
+import android.view.IWindowManager
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.DisplayAreaInfo
+import android.window.WindowContainerTransaction
+import androidx.test.filters.SmallTest
+import com.android.dx.mockito.inline.extended.ExtendedMockito.never
+import com.android.wm.shell.MockToken
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.DisplayController.OnDisplaysChangedListener
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.sysui.ShellInit
+import com.android.wm.shell.transition.Transitions
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.isNull
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.whenever
+
+/**
+ * Test class for [DesktopDisplayEventHandler]
+ *
+ * Usage: atest WMShellUnitTests:DesktopDisplayEventHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DesktopDisplayEventHandlerTest : ShellTestCase() {
+
+ @Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var transitions: Transitions
+ @Mock lateinit var displayController: DisplayController
+ @Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
+ @Mock private lateinit var mockWindowManager: IWindowManager
+
+ private lateinit var shellInit: ShellInit
+ private lateinit var handler: DesktopDisplayEventHandler
+
+ @Before
+ fun setUp() {
+ shellInit = spy(ShellInit(testExecutor))
+ whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
+ val tda = DisplayAreaInfo(MockToken().token(), DEFAULT_DISPLAY, 0)
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)).thenReturn(tda)
+ handler =
+ DesktopDisplayEventHandler(
+ context,
+ shellInit,
+ transitions,
+ displayController,
+ rootTaskDisplayAreaOrganizer,
+ mockWindowManager,
+ )
+ shellInit.init()
+ }
+
+ private fun testDisplayWindowingModeSwitch(
+ defaultWindowingMode: Int,
+ extendedDisplayEnabled: Boolean,
+ expectTransition: Boolean
+ ) {
+ val externalDisplayId = 100
+ val captor = ArgumentCaptor.forClass(OnDisplaysChangedListener::class.java)
+ verify(displayController).addDisplayWindowListener(captor.capture())
+ val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
+ tda.configuration.windowConfiguration.windowingMode = defaultWindowingMode
+ whenever(mockWindowManager.getWindowingMode(anyInt())).thenAnswer { defaultWindowingMode }
+ val settingsSession = ExtendedDisplaySettingsSession(
+ context.contentResolver, if (extendedDisplayEnabled) 1 else 0)
+
+ settingsSession.use {
+ // The external display connected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY, externalDisplayId))
+ captor.value.onDisplayAdded(externalDisplayId)
+ tda.configuration.windowConfiguration.windowingMode = WINDOWING_MODE_FREEFORM
+ // The external display disconnected.
+ whenever(rootTaskDisplayAreaOrganizer.getDisplayIds())
+ .thenReturn(intArrayOf(DEFAULT_DISPLAY))
+ captor.value.onDisplayRemoved(externalDisplayId)
+
+ if (expectTransition) {
+ val arg = argumentCaptor<WindowContainerTransaction>()
+ verify(transitions, times(2)).startTransition(eq(TRANSIT_CHANGE), arg.capture(), isNull())
+ assertThat(arg.firstValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ assertThat(arg.secondValue.changes[tda.token.asBinder()]?.windowingMode)
+ .isEqualTo(defaultWindowingMode)
+ } else {
+ verify(transitions, never()).startTransition(eq(TRANSIT_CHANGE), any(), isNull())
+ }
+ }
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_extendedDisplayDisabled() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = false,
+ expectTransition = false
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_fullscreenDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FULLSCREEN,
+ extendedDisplayEnabled = true,
+ expectTransition = true
+ )
+ }
+
+ @Test
+ fun displayWindowingModeSwitchOnDisplayConnected_freeformDisplay() {
+ testDisplayWindowingModeSwitch(
+ defaultWindowingMode = WINDOWING_MODE_FREEFORM,
+ extendedDisplayEnabled = true,
+ expectTransition = false
+ )
+ }
+
+ private class ExtendedDisplaySettingsSession(
+ private val contentResolver: ContentResolver,
+ private val overrideValue: Int
+ ) : AutoCloseable {
+ private val settingName = DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS
+ private val initialValue = Settings.Global.getInt(contentResolver, settingName, 0)
+
+ init { Settings.Global.putInt(contentResolver, settingName, overrideValue) }
+
+ override fun close() {
+ Settings.Global.putInt(contentResolver, settingName, initialValue)
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
index df061e368071..b06c2dad4ffc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopMixedTransitionHandlerTest.kt
@@ -447,6 +447,37 @@ class DesktopMixedTransitionHandlerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
+ fun startAnimation_pendingTransition_noLaunchChange_returnsFalse() {
+ val wct = WindowContainerTransaction()
+ val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
+ val nonLaunchTaskChange = createChange(createTask(WINDOWING_MODE_FREEFORM))
+ val transition = Binder()
+ whenever(transitions.startTransition(eq(TRANSIT_OPEN), eq(wct), anyOrNull()))
+ .thenReturn(transition)
+ mixedHandler.addPendingMixedTransition(
+ PendingMixedTransition.Launch(
+ transition = transition,
+ launchingTask = launchingTask.taskId,
+ minimizingTask = null,
+ exitingImmersiveTask = null,
+ )
+ )
+
+ val started = mixedHandler.startAnimation(
+ transition,
+ createTransitionInfo(
+ TRANSIT_OPEN,
+ listOf(nonLaunchTaskChange)
+ ),
+ SurfaceControl.Transaction(),
+ SurfaceControl.Transaction(),
+ ) { }
+
+ assertFalse("Should not start animation without launching desktop task", started)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_APP_LAUNCH_TRANSITIONS)
fun addPendingAndAnimateLaunchTransition_noMinimizeChange_doesNotReparentMinimizeChange() {
val wct = WindowContainerTransaction()
val launchingTask = createTask(WINDOWING_MODE_FREEFORM)
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 b157d557c1d8..315a46fcbd7b 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
@@ -1123,11 +1123,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithStyleFloating_taskIsMovedToDesktop() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithoutDisplay_taskIsMovedToDesktop() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
+ isTopActivityNoDisplay = true
numActivities = 1
}
@@ -1139,11 +1139,11 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_topActivityTranslucentWithoutStyleFloating_doesNothing() {
+ fun moveRunningTaskToDesktop_topActivityTranslucentWithDisplay_doesNothing() {
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
@@ -1153,20 +1153,41 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun moveRunningTaskToDesktop_systemUIActivity_doesNothing() {
- val task = setUpFullscreenTask()
-
+ fun moveRunningTaskToDesktop_systemUIActivityWithDisplay_doesNothing() {
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
verifyEnterDesktopWCTNotExecuted()
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun moveRunningTaskToDesktop_systemUIActivityWithoutDisplay_doesNothing() {
+ // Set task as systemUI package
+ val systemUIPackageName = context.resources.getString(
+ com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ controller.moveRunningTaskToDesktop(task, transitionSource = UNKNOWN)
+
+ val wct = getLatestEnterDesktopWct()
+ assertThat(wct.changes[task.token.asBinder()]?.windowingMode).isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
fun moveRunningTaskToDesktop_deviceSupported_taskIsMovedToDesktop() {
val task = setUpFullscreenTask()
@@ -2223,14 +2244,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithStyleFloating_returnSwitchToFreeformWCT() {
+ fun handleRequest_topActivityTransparentWithoutDisplay_returnSwitchToFreeformWCT() {
val freeformTask = setUpFreeformTask()
markTaskVisible(freeformTask)
val task =
setUpFullscreenTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
+ isTopActivityNoDisplay = true
numActivities = 1
}
@@ -2241,11 +2262,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_topActivityTransparentWithoutStyleFloating_returnSwitchToFullscreenWCT() {
+ fun handleRequest_topActivityTransparentWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
val task =
setUpFreeformTask().apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
@@ -2256,14 +2280,19 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun handleRequest_systemUIActivity_returnSwitchToFullscreenWCT() {
- val task = setUpFreeformTask()
+ fun handleRequest_systemUIActivityWithDisplay_returnSwitchToFullscreenWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task =
+ setUpFreeformTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
val result = controller.handleRequest(Binder(), createTransition(task))
assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
@@ -2271,6 +2300,27 @@ class DesktopTasksControllerTest : ShellTestCase() {
}
@Test
+ @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
+ fun handleRequest_systemUIActivityWithoutDisplay_returnSwitchToFreeformWCT() {
+ val freeformTask = setUpFreeformTask()
+ markTaskVisible(freeformTask)
+
+ // Set task as systemUI package
+ val systemUIPackageName = context.resources.getString(
+ com.android.internal.R.string.config_systemUi)
+ val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
+ val task =
+ setUpFullscreenTask().apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = true
+ }
+
+ val result = controller.handleRequest(Binder(), createTransition(task))
+ assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+ .isEqualTo(WINDOWING_MODE_FREEFORM)
+ }
+
+ @Test
@DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,)
fun handleRequest_backTransition_singleTaskNoToken_noWallpaper_doesNotHandle() {
val task = setUpFreeformTask()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
new file mode 100644
index 000000000000..a4008c1e6995
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipEnterAnimatorTest.java
@@ -0,0 +1,201 @@
+/*
+ * 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.pip2.animation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Resources;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+import android.view.SurfaceControl;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.pip2.PipSurfaceTransactionHelper;
+import com.android.wm.shell.pip2.phone.PipAppIconOverlay;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test again {@link PipEnterAnimator}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipEnterAnimatorTest {
+
+ @Mock private Context mMockContext;
+
+ @Mock private Resources mMockResources;
+
+ @Mock private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mMockFactory;
+
+ @Mock private SurfaceControl.Transaction mMockAnimateTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockStartTransaction;
+
+ @Mock private SurfaceControl.Transaction mMockFinishTransaction;
+
+ @Mock private Runnable mMockStartCallback;
+
+ @Mock private Runnable mMockEndCallback;
+
+ @Mock private PipAppIconOverlay mMockPipAppIconOverlay;
+
+ @Mock private SurfaceControl mMockAppIconOverlayLeash;
+
+ @Mock private ActivityInfo mMockActivityInfo;
+
+ @Surface.Rotation private int mRotation;
+ private SurfaceControl mTestLeash;
+ private Rect mEndBounds;
+ private PipEnterAnimator mPipEnterAnimator;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockResources.getInteger(anyInt())).thenReturn(0);
+ when(mMockFactory.getTransaction()).thenReturn(mMockAnimateTransaction);
+ when(mMockAnimateTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockAnimateTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockStartTransaction);
+ when(mMockFinishTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockPipAppIconOverlay.getLeash()).thenReturn(mMockAppIconOverlayLeash);
+
+ mTestLeash = new SurfaceControl.Builder()
+ .setContainerLayer()
+ .setName("PipExpandAnimatorTest")
+ .setCallsite("PipExpandAnimatorTest")
+ .build();
+ }
+
+ @Test
+ public void setAnimationStartCallback_enter_callbackStartCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.pause();
+ });
+
+ verify(mMockStartCallback).run();
+ verifyZeroInteractions(mMockEndCallback);
+ }
+
+ @Test
+ public void setAnimationEndCallback_enter_callbackStartAndEndCallback() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+
+ mPipEnterAnimator.setAnimationStartCallback(mMockStartCallback);
+ mPipEnterAnimator.setAnimationEndCallback(mMockEndCallback);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ mPipEnterAnimator.start();
+ mPipEnterAnimator.end();
+ });
+
+ verify(mMockStartCallback).run();
+ verify(mMockEndCallback).run();
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenGetContentOverlayLeash_returnOverlayLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+
+ assertEquals(mPipEnterAnimator.getContentOverlayLeash(), mMockAppIconOverlayLeash);
+ }
+
+ @Test
+ public void setAppIconContentOverlay_thenClearAppIconOverlay_returnNullLeash() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.clearAppIconOverlay();
+
+ assertNull(mPipEnterAnimator.getContentOverlayLeash());
+ }
+
+ @Test
+ public void onEnterAnimationUpdate_withContentOverlay_animateOverlay() {
+ mRotation = Surface.ROTATION_0;
+ mEndBounds = new Rect(100, 100, 500, 500);
+ mPipEnterAnimator = new PipEnterAnimator(mMockContext, mTestLeash,
+ mMockStartTransaction, mMockFinishTransaction,
+ mEndBounds, mRotation);
+ mPipEnterAnimator.setSurfaceControlTransactionFactory(mMockFactory);
+ mPipEnterAnimator.setPipAppIconOverlaySupplier(
+ (context, appBounds, endBounds, icon, iconSize) -> mMockPipAppIconOverlay);
+
+ float fraction = 0.5f;
+ mPipEnterAnimator.setAppIconContentOverlay(mMockContext, mEndBounds, mEndBounds,
+ mMockActivityInfo, 64 /* iconSize */);
+ mPipEnterAnimator.onEnterAnimationUpdate(fraction, mMockAnimateTransaction);
+
+ verify(mMockPipAppIconOverlay).onAnimationUpdate(
+ eq(mMockAnimateTransaction), anyFloat(), eq(fraction), eq(mEndBounds));
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
index e19a10a78417..b816f0ef041e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/animation/PipExpandAnimatorTest.java
@@ -93,6 +93,17 @@ public class PipExpandAnimatorTest {
.thenReturn(mMockTransaction);
when(mMockTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
.thenReturn(mMockTransaction);
+ // No-op on the mMockStartTransaction
+ when(mMockStartTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setCrop(any(SurfaceControl.class), any(Rect.class)))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setMatrix(any(SurfaceControl.class), any(Matrix.class), any()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setCornerRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
+ when(mMockStartTransaction.setShadowRadius(any(SurfaceControl.class), anyFloat()))
+ .thenReturn(mMockFinishTransaction);
// Do the same for mMockFinishTransaction
when(mMockFinishTransaction.setAlpha(any(SurfaceControl.class), anyFloat()))
.thenReturn(mMockFinishTransaction);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
index 0c100fca2036..2b30bc360d06 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedTaskInfoTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.recents
import android.app.ActivityManager
+import android.app.TaskInfo
import android.graphics.Rect
import android.os.Parcel
import android.testing.AndroidTestingRunner
@@ -24,11 +25,10 @@ import android.window.IWindowContainerToken
import android.window.WindowContainerToken
import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.CREATOR
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_FREEFORM
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SINGLE
-import com.android.wm.shell.shared.GroupedRecentTaskInfo.TYPE_SPLIT
+import com.android.wm.shell.shared.GroupedTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FREEFORM
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_FULLSCREEN
+import com.android.wm.shell.shared.GroupedTaskInfo.TYPE_SPLIT
import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Correspondence
@@ -39,15 +39,15 @@ import org.junit.runner.RunWith
import org.mockito.Mockito.mock
/**
- * Tests for [GroupedRecentTaskInfo]
+ * Tests for [GroupedTaskInfo]
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class GroupedRecentTaskInfoTest : ShellTestCase() {
+class GroupedTaskInfoTest : ShellTestCase() {
@Test
fun testSingleTask_hasCorrectType() {
- assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_SINGLE)
+ assertThat(singleTaskGroupInfo().type).isEqualTo(TYPE_FULLSCREEN)
}
@Test
@@ -117,8 +117,9 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
- assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SINGLE)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
+ assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FULLSCREEN)
assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
assertThat(recentTaskInfoParcel.taskInfo2).isNull()
}
@@ -130,7 +131,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_SPLIT)
assertThat(recentTaskInfoParcel.taskInfo1.taskId).isEqualTo(1)
assertThat(recentTaskInfoParcel.taskInfo2).isNotNull()
@@ -146,11 +148,12 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
recentTaskInfo.writeToParcel(parcel, 0)
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
assertThat(recentTaskInfoParcel.taskInfoList).hasSize(3)
// Only compare task ids
- val taskIdComparator = Correspondence.transforming<ActivityManager.RecentTaskInfo, Int>(
+ val taskIdComparator = Correspondence.transforming<TaskInfo, Int>(
{ it?.taskId }, "has taskId of"
)
assertThat(recentTaskInfoParcel.taskInfoList).comparingElementsUsing(taskIdComparator)
@@ -167,7 +170,8 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
parcel.setDataPosition(0)
// Read the object back from the parcel
- val recentTaskInfoParcel = CREATOR.createFromParcel(parcel)
+ val recentTaskInfoParcel: GroupedTaskInfo =
+ GroupedTaskInfo.CREATOR.createFromParcel(parcel)
assertThat(recentTaskInfoParcel.type).isEqualTo(TYPE_FREEFORM)
assertThat(recentTaskInfoParcel.minimizedTaskIds).isEqualTo(arrayOf(2).toIntArray())
}
@@ -177,24 +181,24 @@ class GroupedRecentTaskInfoTest : ShellTestCase() {
token = WindowContainerToken(mock(IWindowContainerToken::class.java))
}
- private fun singleTaskGroupInfo(): GroupedRecentTaskInfo {
+ private fun singleTaskGroupInfo(): GroupedTaskInfo {
val task = createTaskInfo(id = 1)
- return GroupedRecentTaskInfo.forSingleTask(task)
+ return GroupedTaskInfo.forFullscreenTasks(task)
}
- private fun splitTasksGroupInfo(): GroupedRecentTaskInfo {
+ private fun splitTasksGroupInfo(): GroupedTaskInfo {
val task1 = createTaskInfo(id = 1)
val task2 = createTaskInfo(id = 2)
val splitBounds = SplitBounds(Rect(), Rect(), 1, 2, SNAP_TO_2_50_50)
- return GroupedRecentTaskInfo.forSplitTasks(task1, task2, splitBounds)
+ return GroupedTaskInfo.forSplitTasks(task1, task2, splitBounds)
}
private fun freeformTasksGroupInfo(
freeformTaskIds: Array<Int>,
minimizedTaskIds: Array<Int> = emptyArray()
- ): GroupedRecentTaskInfo {
- return GroupedRecentTaskInfo.forFreeformTasks(
- freeformTaskIds.map { createTaskInfo(it) }.toTypedArray(),
+ ): GroupedTaskInfo {
+ return GroupedTaskInfo.forFreeformTasks(
+ freeformTaskIds.map { createTaskInfo(it) }.toList(),
minimizedTaskIds.toSet())
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 9b73d53e0639..dede583ca970 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -46,6 +46,7 @@ import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
import android.app.ActivityManager;
+import android.app.ActivityManager.RecentTaskInfo;
import android.app.ActivityTaskManager;
import android.app.KeyguardManager;
import android.content.ComponentName;
@@ -71,7 +72,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopRepository;
-import com.android.wm.shell.shared.GroupedRecentTaskInfo;
+import com.android.wm.shell.shared.GroupedTaskInfo;
import com.android.wm.shell.shared.ShellSharedConstants;
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.split.SplitBounds;
@@ -193,8 +194,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddRemoveSplitNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
@@ -207,8 +208,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testAddSameSplitBoundsInfoSkipNotifyChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
// Verify only one update if the split info is the same
@@ -223,13 +224,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertGroupedTasksListEquals(recentTasks,
t1.taskId, -1,
t2.taskId, -1,
@@ -238,12 +239,12 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_withPairs() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
@@ -255,8 +256,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertGroupedTasksListEquals(recentTasks,
t1.taskId, -1,
t2.taskId, t4.taskId,
@@ -267,14 +268,14 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
@SuppressWarnings("unchecked")
- final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
- Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
- ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ final List<GroupedTaskInfo>[] recentTasks = new List[1];
+ Consumer<List<GroupedTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t6 = makeTaskInfo(6);
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
@@ -287,7 +288,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
mRecentTasksController.asRecentTasks()
- .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+ .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run,
+ consumer);
mMainExecutor.flushAll();
assertGroupedTasksListEquals(recentTasks[0],
@@ -299,28 +301,28 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_groupFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 freeform tasks should be grouped into one, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
// Check freeform group entries
assertEquals(t1, freeformGroup.getTaskInfoList().get(0));
@@ -333,11 +335,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_hasActiveDesktopTasks_proto2Enabled_freeformTaskOrder() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
SplitBounds pair1Bounds =
@@ -347,19 +349,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 2 split screen tasks grouped, 2 freeform tasks grouped, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo splitGroup = recentTasks.get(0);
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup = recentTasks.get(2);
+ GroupedTaskInfo splitGroup = recentTasks.get(0);
+ GroupedTaskInfo freeformGroup = recentTasks.get(1);
+ GroupedTaskInfo singleGroup = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_SPLIT, splitGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_SPLIT, splitGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup.getType());
// Check freeform group entries
assertEquals(t3, freeformGroup.getTaskInfoList().get(0));
@@ -378,24 +380,24 @@ public class RecentTasksControllerTest extends ShellTestCase {
ExtendedMockito.doReturn(false)
.when(() -> DesktopModeStatus.canEnterDesktopMode(any()));
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
setRawList(t1, t2, t3, t4);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// Expect no grouping of tasks
assertEquals(4, recentTasks.size());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(0).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(1).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(2).getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, recentTasks.get(3).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(0).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(1).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(2).getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, recentTasks.get(3).getType());
assertEquals(t1, recentTasks.get(0).getTaskInfo1());
assertEquals(t2, recentTasks.get(1).getTaskInfo1());
@@ -405,11 +407,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testGetRecentTasks_proto2Enabled_includesMinimizedFreeformTasks() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
- ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
- ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t4 = makeTaskInfo(4);
+ RecentTaskInfo t5 = makeTaskInfo(5);
setRawList(t1, t2, t3, t4, t5);
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
@@ -417,19 +419,19 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(5)).thenReturn(true);
when(mDesktopRepository.isMinimizedTask(3)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
// 3 freeform tasks should be grouped into one, 2 single tasks, 3 total recents entries
assertEquals(3, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
- GroupedRecentTaskInfo singleGroup1 = recentTasks.get(1);
- GroupedRecentTaskInfo singleGroup2 = recentTasks.get(2);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo singleGroup1 = recentTasks.get(1);
+ GroupedTaskInfo singleGroup2 = recentTasks.get(2);
// Check that groups have expected types
- assertEquals(GroupedRecentTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup1.getType());
- assertEquals(GroupedRecentTaskInfo.TYPE_SINGLE, singleGroup2.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FREEFORM, freeformGroup.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup1.getType());
+ assertEquals(GroupedTaskInfo.TYPE_FULLSCREEN, singleGroup2.getType());
// Check freeform group entries
assertEquals(3, freeformGroup.getTaskInfoList().size());
@@ -445,8 +447,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
@EnableFlags(FLAG_ENABLE_DESKTOP_WINDOWING_PERSISTENCE)
public void testGetRecentTasks_hasDesktopTasks_persistenceEnabled_freeformTaskHaveBoundsSet() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
t1.lastNonFullscreenBounds = new Rect(100, 200, 300, 400);
t2.lastNonFullscreenBounds = new Rect(150, 250, 350, 450);
@@ -455,11 +457,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mDesktopRepository.isActiveTask(1)).thenReturn(true);
when(mDesktopRepository.isActiveTask(2)).thenReturn(true);
- ArrayList<GroupedRecentTaskInfo> recentTasks = mRecentTasksController.getRecentTasks(
- MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
+ ArrayList<GroupedTaskInfo> recentTasks =
+ mRecentTasksController.getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0);
assertEquals(1, recentTasks.size());
- GroupedRecentTaskInfo freeformGroup = recentTasks.get(0);
+ GroupedTaskInfo freeformGroup = recentTasks.get(0);
// Check bounds
assertEquals(t1.lastNonFullscreenBounds, freeformGroup.getTaskInfoList().get(
@@ -478,9 +480,9 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testRemovedTaskRemovesSplit() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
- ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
- ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t2 = makeTaskInfo(2);
+ RecentTaskInfo t3 = makeTaskInfo(3);
setRawList(t1, t2, t3);
// Add a pair
@@ -500,7 +502,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
@Test
public void testTaskWindowingModeChangedNotifiesChange() {
- ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ RecentTaskInfo t1 = makeTaskInfo(1);
setRawList(t1);
// Remove one of the tasks and ensure the pair is removed
@@ -607,7 +609,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksControllerReal.onTaskMovedToFrontThroughTransition(taskInfo);
- verify(mRecentTasksListener).onTaskMovedToFront(taskInfo);
+ GroupedTaskInfo runningTask = GroupedTaskInfo.forFullscreenTasks(taskInfo);
+ verify(mRecentTasksListener).onTaskMovedToFront(eq(new GroupedTaskInfo[] { runningTask }));
}
@Test
@@ -656,8 +659,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to create a task with a given task id.
*/
- private ActivityManager.RecentTaskInfo makeTaskInfo(int taskId) {
- ActivityManager.RecentTaskInfo info = new ActivityManager.RecentTaskInfo();
+ private RecentTaskInfo makeTaskInfo(int taskId) {
+ RecentTaskInfo info = new RecentTaskInfo();
info.taskId = taskId;
info.lastNonFullscreenBounds = new Rect();
return info;
@@ -676,10 +679,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
/**
* Helper to set the raw task list on the controller.
*/
- private ArrayList<ActivityManager.RecentTaskInfo> setRawList(
- ActivityManager.RecentTaskInfo... tasks) {
- ArrayList<ActivityManager.RecentTaskInfo> rawList = new ArrayList<>();
- for (ActivityManager.RecentTaskInfo task : tasks) {
+ private ArrayList<RecentTaskInfo> setRawList(
+ RecentTaskInfo... tasks) {
+ ArrayList<RecentTaskInfo> rawList = new ArrayList<>();
+ for (RecentTaskInfo task : tasks) {
rawList.add(task);
}
doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
@@ -693,11 +696,11 @@ public class RecentTasksControllerTest extends ShellTestCase {
* @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
* the grouped task list
*/
- private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
+ private void assertGroupedTasksListEquals(List<GroupedTaskInfo> recentTasks,
int... expectedTaskIds) {
int[] flattenedTaskIds = new int[recentTasks.size() * 2];
for (int i = 0; i < recentTasks.size(); i++) {
- GroupedRecentTaskInfo pair = recentTasks.get(i);
+ GroupedTaskInfo pair = recentTasks.get(i);
int taskId1 = pair.getTaskInfo1().taskId;
flattenedTaskIds[2 * i] = taskId1;
flattenedTaskIds[2 * i + 1] = pair.getTaskInfo2() != null
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index ef3af8e7bdac..966651f19711 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -217,7 +217,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
// Put the same component to the top running task
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null,
@@ -238,7 +237,6 @@ public class SplitScreenControllerTests extends ShellTestCase {
// Put the same component to the top running task
ActivityManager.RunningTaskInfo topRunningTask =
createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
- doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask();
doReturn(topRunningTask).when(mRecentTasks).getTopRunningTask(any());
// Put the same component into a task in the background
ActivityManager.RecentTaskInfo sameTaskInfo = new ActivityManager.RecentTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index 03aab18d8d87..56267174ba75 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -478,25 +478,10 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun testDecorationIsCreatedForTopTranslucentActivitiesWithStyleFloating() {
+ fun testDecorationIsNotCreatedForTopTranslucentActivities() {
val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
isTopActivityTransparent = true
- isTopActivityStyleFloating = true
- numActivities = 1
- }
- doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(any()) }
- setUpMockDecorationsForTasks(task)
-
- onTaskOpening(task)
- assertTrue(windowDecorByTaskIdSpy.contains(task.taskId))
- }
-
- @Test
- @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
- fun testDecorationIsNotCreatedForTopTranslucentActivitiesWithoutStyleFloating() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
- isTopActivityTransparent = true
- isTopActivityStyleFloating = false
+ isTopActivityNoDisplay = false
numActivities = 1
}
onTaskOpening(task)
@@ -507,13 +492,14 @@ class DesktopModeWindowDecorViewModelTests : ShellTestCase() {
@Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODALS_POLICY)
fun testDecorationIsNotCreatedForSystemUIActivities() {
- val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN)
-
// Set task as systemUI package
val systemUIPackageName = context.resources.getString(
com.android.internal.R.string.config_systemUi)
val baseComponent = ComponentName(systemUIPackageName, /* class */ "")
- task.baseActivity = baseComponent
+ val task = createTask(windowingMode = WINDOWING_MODE_FULLSCREEN).apply {
+ baseActivity = baseComponent
+ isTopActivityNoDisplay = false
+ }
onTaskOpening(task)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
index 80ad1df44a1b..d44c01592ff7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingDecorViewModelTest.kt
@@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopRepository
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.ReturnToDragStartAnimator
@@ -52,6 +53,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
private val transitionsMock: Transitions = mock()
private val shellTaskOrganizerMock: ShellTaskOrganizer = mock()
private val desktopRepository: DesktopRepository = mock()
+ private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val toggleResizeDesktopTaskTransitionHandlerMock:
ToggleResizeDesktopTaskTransitionHandler =
mock()
@@ -74,6 +76,7 @@ class DesktopTilingDecorViewModelTest : ShellTestCase() {
toggleResizeDesktopTaskTransitionHandlerMock,
returnToDragStartAnimatorMock,
desktopRepository,
+ desktopModeEventLogger,
)
whenever(contextMock.createContextAsUser(any(), any())).thenReturn(contextMock)
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
index f371f5223419..057d8fa3adb0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/DesktopTilingWindowDecorationTest.kt
@@ -21,6 +21,7 @@ import android.content.res.Resources
import android.graphics.Rect
import android.os.IBinder
import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_CHANGE
import android.view.WindowManager.TRANSIT_TO_FRONT
@@ -33,6 +34,8 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.DisplayLayout
import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger
import com.android.wm.shell.desktopmode.DesktopRepository
import com.android.wm.shell.desktopmode.DesktopTasksController
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -91,7 +94,9 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
private val info: TransitionInfo = mock()
private val finishCallback: Transitions.TransitionFinishCallback = mock()
private val desktopRepository: DesktopRepository = mock()
+ private val desktopModeEventLogger: DesktopModeEventLogger = mock()
private val desktopTilingDividerWindowManager: DesktopTilingDividerWindowManager = mock()
+ private val motionEvent: MotionEvent = mock()
private lateinit var tilingDecoration: DesktopTilingWindowDecoration
private val split_divider_width = 10
@@ -112,6 +117,7 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
toggleResizeDesktopTaskTransitionHandler,
returnToDragStartAnimator,
desktopRepository,
+ desktopModeEventLogger,
)
whenever(context.createContextAsUser(any(), any())).thenReturn(context)
}
@@ -371,13 +377,13 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
// End moving, no startTransition because bounds did not change.
tiledTaskHelper.newBounds.set(BOUNDS)
- tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
verify(tiledTaskHelper, times(2)).hideVeil()
verify(transitions, never()).startTransition(any(), any(), any())
// Move then end again with bounds changing to ensure startTransition is called.
tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
- tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
verify(transitions, times(1))
.startTransition(eq(TRANSIT_CHANGE), any(), eq(tilingDecoration))
// No hide veil until start animation is called.
@@ -389,6 +395,64 @@ class DesktopTilingWindowDecorationTest : ShellTestCase() {
}
@Test
+ fun tiledTasksResizedUsingDividerHandle_shouldLogResizingEvents() {
+ // Setup
+ val task1 = createFreeformTask()
+ val task2 = createFreeformTask()
+ val stableBounds = STABLE_BOUNDS_MOCK
+ whenever(displayController.getDisplayLayout(any())).thenReturn(displayLayout)
+ whenever(displayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(stableBounds)
+ }
+ whenever(context.resources).thenReturn(resources)
+ whenever(resources.getDimensionPixelSize(any())).thenReturn(split_divider_width)
+ desktopWindowDecoration.mTaskInfo = task1
+ task1.minWidth = 0
+ task1.minHeight = 0
+ initTiledTaskHelperMock(task1)
+ desktopWindowDecoration.mDecorWindowContext = context
+ whenever(resources.getBoolean(any())).thenReturn(true)
+
+ // Act
+ tilingDecoration.onAppTiled(
+ task1,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.RIGHT,
+ BOUNDS,
+ )
+ tilingDecoration.onAppTiled(
+ task2,
+ desktopWindowDecoration,
+ DesktopTasksController.SnapPosition.LEFT,
+ BOUNDS,
+ )
+ tilingDecoration.leftTaskResizingHelper = tiledTaskHelper
+ tilingDecoration.rightTaskResizingHelper = tiledTaskHelper
+ tilingDecoration.onDividerHandleDragStart(motionEvent)
+ // Log start event for task1 and task2, but the tasks are the same in
+ // this test, so we verify the same log twice.
+ verify(desktopModeEventLogger, times(2)).logTaskResizingStarted(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ task1,
+ displayController,
+ )
+
+ tilingDecoration.onDividerHandleMoved(BOUNDS, transaction)
+ tilingDecoration.onDividerHandleDragEnd(BOUNDS, transaction, motionEvent)
+ // Log end event for task1 and task2, but the tasks are the same in
+ // this test, so we verify the same log twice.
+ verify(desktopModeEventLogger, times(2)).logTaskResizingEnded(
+ ResizeTrigger.TILING_DIVIDER,
+ motionEvent,
+ task1,
+ BOUNDS.height(),
+ BOUNDS.width(),
+ displayController,
+ )
+ }
+
+ @Test
fun taskTiled_shouldBeRemoved_whenTileBroken() {
val task1 = createFreeformTask()
val stableBounds = STABLE_BOUNDS_MOCK
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
index c96ce955f217..734815cdd915 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/tiling/TilingDividerViewTest.kt
@@ -68,7 +68,7 @@ class TilingDividerViewTest : ShellTestCase() {
val downMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
whenever(dividerMoveCallbackMock.onDividerMove(any())).thenReturn(true)
val motionEvent =
@@ -79,7 +79,7 @@ class TilingDividerViewTest : ShellTestCase() {
val upMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMovedEnd(any(), any())
}
@Test
@@ -92,12 +92,12 @@ class TilingDividerViewTest : ShellTestCase() {
val downMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_DOWN, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, downMotionEvent)
- verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any())
+ verify(dividerMoveCallbackMock, times(1)).onDividerMoveStart(any(), any())
val upMotionEvent =
getMotionEvent(downTime, MotionEvent.ACTION_UP, x.toFloat(), y.toFloat())
tilingDividerView.handleMotionEvent(viewMock, upMotionEvent)
- verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any())
+ verify(dividerMoveCallbackMock, never()).onDividerMovedEnd(any(), any())
}
private fun getMotionEvent(eventTime: Long, action: Int, x: Float, y: Float): MotionEvent {
diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java
index 55c8ed5debf8..530d48d3e60b 100644
--- a/media/java/android/media/ImageReader.java
+++ b/media/java/android/media/ImageReader.java
@@ -40,6 +40,8 @@ import android.os.ParcelFileDescriptor;
import android.os.Trace;
import android.view.Surface;
+import com.android.internal.camera.flags.Flags;
+
import dalvik.system.VMRuntime;
import java.io.IOException;
@@ -1208,6 +1210,11 @@ public class ImageReader implements AutoCloseable {
default:
width = nativeGetWidth();
}
+ if (Flags.cameraHeifGainmap()) {
+ if (getFormat() == ImageFormat.HEIC_ULTRAHDR){
+ width = ImageReader.this.getWidth();
+ }
+ }
return width;
}
@@ -1227,6 +1234,11 @@ public class ImageReader implements AutoCloseable {
default:
height = nativeGetHeight();
}
+ if (Flags.cameraHeifGainmap()) {
+ if (getFormat() == ImageFormat.HEIC_ULTRAHDR){
+ height = ImageReader.this.getHeight();
+ }
+ }
return height;
}
diff --git a/media/java/android/media/ImageUtils.java b/media/java/android/media/ImageUtils.java
index f4caad727407..c7678067f0b5 100644
--- a/media/java/android/media/ImageUtils.java
+++ b/media/java/android/media/ImageUtils.java
@@ -23,6 +23,8 @@ import android.media.Image.Plane;
import android.util.Log;
import android.util.Size;
+import com.android.internal.camera.flags.Flags;
+
import libcore.io.Memory;
import java.nio.ByteBuffer;
@@ -44,6 +46,11 @@ class ImageUtils {
* are used.
*/
public static int getNumPlanesForFormat(int format) {
+ if (Flags.cameraHeifGainmap()) {
+ if (format == ImageFormat.HEIC_ULTRAHDR) {
+ return 1;
+ }
+ }
switch (format) {
case ImageFormat.YV12:
case ImageFormat.YUV_420_888:
@@ -229,6 +236,11 @@ class ImageUtils {
public static int getEstimatedNativeAllocBytes(int width, int height, int format,
int numImages) {
double estimatedBytePerPixel;
+ if (Flags.cameraHeifGainmap()) {
+ if (format == ImageFormat.HEIC_ULTRAHDR) {
+ estimatedBytePerPixel = 0.3;
+ }
+ }
switch (format) {
// 10x compression from RGB_888
case ImageFormat.JPEG:
@@ -283,6 +295,11 @@ class ImageUtils {
}
private static Size getEffectivePlaneSizeForImage(Image image, int planeIdx) {
+ if (Flags.cameraHeifGainmap()) {
+ if (image.getFormat() == ImageFormat.HEIC_ULTRAHDR){
+ return new Size(image.getWidth(), image.getHeight());
+ }
+ }
switch (image.getFormat()) {
case ImageFormat.YCBCR_P010:
case ImageFormat.YV12:
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index 185f579df4b9..0adc4783445a 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -15,3 +15,10 @@ flag {
description: "Enable B frames for Stagefright recorder."
bug: "341121900"
}
+
+flag {
+ name: "muxer_mp4_enable_apv"
+ namespace: "media_solutions"
+ description: "Enable APV support in mp4 writer."
+ bug: "370061501"
+}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 6658918518ea..abfc24413d56 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -16,6 +16,8 @@
package android.media.tv;
+import static android.media.tv.flags.Flags.tifExtensionStandardization;
+
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -159,6 +161,11 @@ public abstract class TvInputService extends Service {
new RemoteCallbackList<>();
private TvInputManager mTvInputManager;
+ /**
+ * @hide
+ */
+ protected TvInputServiceExtensionManager mTvInputServiceExtensionManager =
+ new TvInputServiceExtensionManager();
@Override
public final IBinder onBind(Intent intent) {
@@ -211,12 +218,23 @@ public abstract class TvInputService extends Service {
}
@Override
- public List<String> getAvailableExtensionInterfaceNames() {
- return TvInputService.this.getAvailableExtensionInterfaceNames();
+ public List<String> getAvailableExtensionInterfaceNames() {
+ List<String> extensionNames =
+ TvInputService.this.getAvailableExtensionInterfaceNames();
+ if (tifExtensionStandardization()) {
+ extensionNames.addAll(
+ TvInputServiceExtensionManager.getStandardExtensionInterfaceNames());
+ }
+ return extensionNames;
}
@Override
public IBinder getExtensionInterface(String name) {
+ if (tifExtensionStandardization() && name != null) {
+ if (TvInputServiceExtensionManager.checkIsStandardizedInterfaces(name)) {
+ return mTvInputServiceExtensionManager.getExtensionIBinder(name);
+ }
+ }
return TvInputService.this.getExtensionInterface(name);
}
diff --git a/media/java/android/media/tv/TvInputServiceExtensionManager.java b/media/java/android/media/tv/TvInputServiceExtensionManager.java
new file mode 100644
index 000000000000..0e98488c93c0
--- /dev/null
+++ b/media/java/android/media/tv/TvInputServiceExtensionManager.java
@@ -0,0 +1,614 @@
+/*
+ * 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.media.tv;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.tv.flags.Flags;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_TIF_EXTENSION_STANDARDIZATION)
+public class TvInputServiceExtensionManager {
+ private static final String TAG = "TvInputServiceExtensionManager";
+ private static final String SCAN_PACKAGE = "android.media.tv.extension.scan.";
+ private static final String OAD_PACKAGE = "android.media.tv.extension.oad.";
+ private static final String CAM_PACKAGE = "android.media.tv.extension.cam.";
+ private static final String RATING_PACKAGE = "android.media.tv.extension.rating.";
+ private static final String TIME_PACKAGE = "android.media.tv.extension.time.";
+ private static final String TELETEXT_PACKAGE = "android.media.tv.extension.teletext.";
+ private static final String SCAN_BSU_PACKAGE = "android.media.tv.extension.scanbsu.";
+ private static final String CLIENT_TOKEN_PACKAGE = "android.media.tv.extension.clienttoken.";
+ private static final String SCREEN_MODE_PACKAGE = "android.media.tv.extension.screenmode.";
+ private static final String SIGNAL_PACKAGE = "android.media.tv.extension.signal.";
+ private static final String SERVICE_DATABASE_PACKAGE = "android.media.tv.extension.servicedb.";
+ private static final String PVR_PACKAGE = "android.media.tv.extension.pvr.";
+ private static final String EVENT_PACKAGE = "android.media.tv.extension.event.";
+ private static final String ANALOG_PACKAGE = "android.media.tv.extension.analog.";
+ private static final String TUNE_PACKAGE = "android.media.tv.extension.tune.";
+
+ /** Register binder returns success when it abides standardized interface structure */
+ public static final int REGISTER_SUCCESS = 0;
+ /** Register binder returns fail when the extension name is not in the standardization list */
+ public static final int REGISTER_FAIL_NAME_NOT_STANDARDIZED = 1;
+ /** Register binder returns fail when the IBinder does not implement standardized interface */
+ public static final int REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED = 2;
+ /** Register binder returns fail when remote server not available */
+ public static final int REGISTER_FAIL_REMOTE_EXCEPTION = 3;
+
+ /**
+ * Interface responsible for creating scan session and obtain parameters.
+ */
+ public static final String ISCAN_INTERFACE = SCAN_PACKAGE + "IScanInterface";
+ /**
+ * Interface that handles scan session and get/store related information.
+ */
+ public static final String ISCAN_SESSION = SCAN_PACKAGE + "IScanSession";
+ /**
+ * Interface that notifies changes related to scan session.
+ */
+ public static final String ISCAN_LISTENER = SCAN_PACKAGE + "IScanListener";
+ /**
+ * Interface for setting HDPlus information.
+ */
+ public static final String IHDPLUS_INFO = SCAN_PACKAGE + "IHDPlusInfo";
+ /**
+ * Interface for handling operator detection for scanning.
+ */
+ public static final String IOPERATOR_DETECTION = SCAN_PACKAGE + "IOperatorDetection";
+ /**
+ * Interface for changes related to operator detection searches.
+ */
+ public static final String IOPERATOR_DETECTION_LISTENER = SCAN_PACKAGE
+ + "IOperatorDetectionListener";
+ /**
+ * Interface for handling region channel list for scanning.
+ */
+ public static final String IREGION_CHANNEL_LIST = SCAN_PACKAGE + "IRegionChannelList";
+ /**
+ * Interface for changes related to changes in region channel list search.
+ */
+ public static final String IREGION_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+ + "IRegionChannelListListener";
+ /**
+ * Interface for handling target region information.
+ */
+ public static final String ITARGET_REGION = SCAN_PACKAGE + "ITargetRegion";
+ /**
+ * Interface for changes related to target regions during scanning.
+ */
+ public static final String ITARGET_REGION_LISTENER = SCAN_PACKAGE + "ITargetRegionListener";
+ /**
+ * Interface for handling LCN conflict groups.
+ */
+ public static final String ILCN_CONFLICT = SCAN_PACKAGE + "ILcnConflict";
+ /**
+ * Interface for detecting LCN conflicts during scanning.
+ */
+ public static final String ILCN_CONFLICT_LISTENER = SCAN_PACKAGE + "ILcnConflictListener";
+ /**
+ * Interface for handling LCN V2 channel list information.
+ */
+ public static final String ILCNV2_CHANNEL_LIST = SCAN_PACKAGE + "ILcnV2ChannelList";
+ /**
+ * Interface for detecting LCN V2 channel list during scanning.
+ */
+ public static final String ILCNV2_CHANNEL_LIST_LISTENER = SCAN_PACKAGE
+ + "ILcnV2ChannelListListener";
+ /**
+ * Interface for handling favorite network related information.
+ */
+ public static final String IFAVORITE_NETWORK = SCAN_PACKAGE + "IFavoriteNetwork";
+ /**
+ * Interface for detecting favorite network during scanning.
+ */
+ public static final String IFAVORITE_NETWORK_LISTENER = SCAN_PACKAGE
+ + "IFavoriteNetworkListener";
+ /**
+ * Interface for handling Turksat channel update system service.
+ */
+ public static final String ITKGS_INFO = SCAN_PACKAGE + "ITkgsInfo";
+ /**
+ * Interface for changes related to TKGS information.
+ */
+ public static final String ITKGS_INFO_LISTENER = SCAN_PACKAGE + "ITkgsInfoListener";
+ /**
+ * Interface for satellite search related to low noise block downconverter.
+ */
+ public static final String ISCAN_SAT_SEARCH = SCAN_PACKAGE + "IScanSatSearch";
+ /**
+ * Interface for Over-the-Air Download.
+ */
+ public static final String IOAD_UPDATE_INTERFACE = OAD_PACKAGE + "IOadUpdateInterface";
+ /**
+ * Interface for handling conditional access module app related information.
+ */
+ public static final String ICAM_APP_INFO_SERVICE = CAM_PACKAGE + "ICamAppInfoService";
+ /**
+ * Interface for changes on conditional access module app related information.
+ */
+ public static final String ICAM_APP_INFO_LISTENER = CAM_PACKAGE + "ICamAppInfoListener";
+ /**
+ * Interface for handling conditional access module related information.
+ */
+ public static final String ICAM_MONITORING_SERVICE = CAM_PACKAGE + "ICamMonitoringService";
+ /**
+ * Interface for changes on conditional access module related information.
+ */
+ public static final String ICAM_INFO_LISTENER = CAM_PACKAGE + "ICamInfoListener";
+ /**
+ * Interface for handling control of CI+ operations.
+ */
+ public static final String ICI_OPERATOR_INTERFACE = CAM_PACKAGE + "ICiOperatorInterface";
+ /**
+ * Interfaces for changes on CI+ operations.
+ */
+ public static final String ICI_OPERATOR_LISTENER = CAM_PACKAGE + "ICiOperatorListener";
+ /**
+ * Interface for handling conditional access module profile related information.
+ */
+ public static final String ICAM_PROFILE_INTERFACE = CAM_PACKAGE + "ICamProfileInterface";
+ /**
+ * Interface for handling conditional access module DRM related information.
+ */
+ public static final String ICONTENT_CONTROL_SERVICE = CAM_PACKAGE + "IContentControlService";
+ /**
+ * Interface for changes on DRM.
+ */
+ public static final String ICAM_DRM_INFO_LISTENER = CAM_PACKAGE + "ICamDrmInfoListener";
+ /**
+ * Interface for handling conditional access module pin related information.
+ */
+ public static final String ICAM_PIN_SERVICE = CAM_PACKAGE + "ICamPinService";
+ /**
+ * Interface for changes on conditional access module pin capability.
+ */
+ public static final String ICAM_PIN_CAPABILITY_LISTENER = CAM_PACKAGE
+ + "ICamPinCapabilityListener";
+ /**
+ * Interface for changes on conditional access module pin status.
+ */
+ public static final String ICAM_PIN_STATUS_LISTENER = CAM_PACKAGE + "ICamPinStatusListener";
+ /**
+ * Interface for handling conditional access module host control service.
+ */
+ public static final String ICAM_HOST_CONTROL_SERVICE = CAM_PACKAGE + "ICamHostControlService";
+ /**
+ * Interface for handling conditional access module ask release reply.
+ */
+ public static final String ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK = CAM_PACKAGE
+ + "ICamHostControlAskReleaseReplyCallback";
+ /**
+ * Interface for changes on conditional access module host control service.
+ */
+ public static final String ICAM_HOST_CONTROL_INFO_LISTENER = CAM_PACKAGE
+ + "ICamHostControlInfoListener";
+ /**
+ * Interface for handling conditional access module host control service tune_quietly_flag.
+ */
+ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG = CAM_PACKAGE
+ + "ICamHostControlTuneQuietlyFlag";
+ /**
+ * Interface for changes on conditional access module host control service tune_quietly_flag.
+ */
+ public static final String ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER = CAM_PACKAGE
+ + "ICamHostControlTuneQuietlyFlagListener";
+ /**
+ * Interface for handling conditional access module multi media interface.
+ */
+ public static final String IMMI_INTERFACE = CAM_PACKAGE + "IMmiInterface";
+ /**
+ * Interface for controlling conditional access module multi media session.
+ */
+ public static final String IMMI_SESSION = CAM_PACKAGE + "IMmiSession";
+ /**
+ * Interface for changes on conditional access module multi media session status.
+ */
+ public static final String IMMI_STATUS_CALLBACK = CAM_PACKAGE + "IMmiStatusCallback";
+ /**
+ * Interface for changes on conditional access app info related to entering menu.
+ */
+ public static final String IENTER_MENU_ERROR_CALLBACK = CAM_PACKAGE + "IEnterMenuErrorCallback";
+ /**
+ * Interface for handling RRT downloadable rating data.
+ */
+ public static final String IDOWNLOADABLE_RATING_TABLE_MONITOR = RATING_PACKAGE
+ + "IDownloadableRatingTableMonitor";
+ /**
+ * Interface for handling RRT rating related information.
+ */
+ public static final String IRATING_INTERFACE = RATING_PACKAGE + "IRatingInterface";
+ /**
+ * Interface for handling PMT rating related information.
+ */
+ public static final String IPMT_RATING_INTERFACE = RATING_PACKAGE + "IPmtRatingInterface";
+ /**
+ * Interface for changes on PMT rating related information.
+ */
+ public static final String IPMT_RATING_LISTENER = RATING_PACKAGE + "IPmtRatingListener";
+ /**
+ * Interface for handling IVBI rating related information.
+ */
+ public static final String IVBI_RATING_INTERFACE = RATING_PACKAGE + "IVbiRatingInterface";
+ /**
+ * Interface for changes on IVBI rating related information.
+ */
+ public static final String IVBI_RATING_LISTENER = RATING_PACKAGE + "IVbiRatingListener";
+ /**
+ * Interface for handling program rating related information.
+ */
+ public static final String IPROGRAM_INFO = RATING_PACKAGE + "IProgramInfo";
+ /**
+ * Interface for changes on program rating related information.
+ */
+ public static final String IPROGRAM_INFO_LISTENER = RATING_PACKAGE + "IProgramInfoListener";
+ /**
+ * Interface for getting broadcast time related information.
+ */
+ public static final String BROADCAST_TIME = TIME_PACKAGE + "BroadcastTime";
+ /**
+ * Interface for handling data service signal information on teletext.
+ */
+ public static final String IDATA_SERVICE_SIGNAL_INFO = TELETEXT_PACKAGE
+ + "IDataServiceSignalInfo";
+ /**
+ * Interface for changes on data service signal information on teletext.
+ */
+ public static final String IDATA_SERVICE_SIGNAL_INFO_LISTENER = TELETEXT_PACKAGE
+ + "IDataServiceSignalInfoListener";
+ /**
+ * Interface for handling teletext page information.
+ */
+ public static final String ITELETEXT_PAGE_SUB_CODE = TELETEXT_PACKAGE + "ITeletextPageSubCode";
+ /**
+ * Interface for handling scan background service update.
+ */
+ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE = SCAN_BSU_PACKAGE
+ + "IScanBackgroundServiceUpdate";
+ /**
+ * Interface for changes on background service update
+ */
+ public static final String ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER = SCAN_BSU_PACKAGE
+ + "IScanBackgroundServiceUpdateListener";
+ /**
+ * Interface for generating client token.
+ */
+ public static final String ICLIENT_TOKEN = CLIENT_TOKEN_PACKAGE + "IClientToken";
+ /**
+ * Interfaces for handling screen mode information.
+ */
+ public static final String ISCREEN_MODE_SETTINGS = SCREEN_MODE_PACKAGE + "IScreenModeSettings";
+ /**
+ * Interfaces for handling HDMI signal information update.
+ */
+ public static final String IHDMI_SIGNAL_INTERFACE = SIGNAL_PACKAGE + "IHdmiSignalInterface";
+ /**
+ * Interfaces for changes on HDMI signal information update.
+ */
+ public static final String IHDMI_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IHdmiSignalInfoListener";
+ /**
+ * Interfaces for handling audio signal information update.
+ */
+ public static final String IAUDIO_SIGNAL_INFO = SIGNAL_PACKAGE + "IAudioSignalInfo";
+ /**
+ * Interfaces for handling analog audio signal information update.
+ */
+ public static final String IANALOG_AUDIO_INFO = SIGNAL_PACKAGE + "IAnalogAudioInfo";
+ /**
+ * Interfaces for change on audio signal information update.
+ */
+ public static final String IAUDIO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IAudioSignalInfoListener";
+ /**
+ * Interfaces for handling video signal information update.
+ */
+ public static final String IVIDEO_SIGNAL_INFO = SIGNAL_PACKAGE + "IVideoSignalInfo";
+ /**
+ * Interfaces for changes on video signal information update.
+ */
+ public static final String IVIDEO_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "IVideoSignalInfoListener";
+ /**
+ * Interfaces for handling service database updates.
+ */
+ public static final String ISERVICE_LIST_EDIT = SERVICE_DATABASE_PACKAGE + "IServiceListEdit";
+ /**
+ * Interfaces for changes on service database updates.
+ */
+ public static final String ISERVICE_LIST_EDIT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListEditListener";
+ /**
+ * Interfaces for getting service database related information.
+ */
+ public static final String ISERVICE_LIST = SERVICE_DATABASE_PACKAGE + "IServiceList";
+ /**
+ * Interfaces for transferring service database related information.
+ */
+ public static final String ISERVICE_LIST_TRANSFER_INTERFACE = SERVICE_DATABASE_PACKAGE
+ + "IServiceListTransferInterface";
+ /**
+ * Interfaces for exporting service database session.
+ */
+ public static final String ISERVICE_LIST_EXPORT_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListExportSession";
+ /**
+ * Interfaces for changes on exporting service database session.
+ */
+ public static final String ISERVICE_LIST_EXPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListExportListener";
+ /**
+ * Interfaces for importing service database session.
+ */
+ public static final String ISERVICE_LIST_IMPORT_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListImportSession";
+ /**
+ * Interfaces for changes on importing service database session.
+ */
+ public static final String ISERVICE_LIST_IMPORT_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListImportListener";
+ /**
+ * Interfaces for setting channel list resources.
+ */
+ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_SESSION = SERVICE_DATABASE_PACKAGE
+ + "IServiceListSetChannelListSession";
+ /**
+ * Interfaces for changes on setting channel list resources.
+ */
+ public static final String ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER = SERVICE_DATABASE_PACKAGE
+ + "IServiceListSetChannelListListener";
+ /**
+ * Interfaces for transferring channel list resources.
+ */
+ public static final String ICHANNEL_LIST_TRANSFER = SERVICE_DATABASE_PACKAGE
+ + "IChannelListTransfer";
+ /**
+ * Interfaces for record contents updates.
+ */
+ public static final String IRECORDED_CONTENTS = PVR_PACKAGE + "IRecordedContents";
+ /**
+ * Interfaces for changes on deleting record contents.
+ */
+ public static final String IDELETE_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+ + "IDeleteRecordedContentsCallback";
+ /**
+ * Interfaces for changes on getting record contents.
+ */
+ public static final String IGET_INFO_RECORDED_CONTENTS_CALLBACK = PVR_PACKAGE
+ + "IGetInfoRecordedContentsCallback";
+ /**
+ * Interfaces for monitoring present event information.
+ */
+ public static final String IEVENT_MONITOR = EVENT_PACKAGE + "IEventMonitor";
+ /**
+ * Interfaces for changes on present event information.
+ */
+ public static final String IEVENT_MONITOR_LISTENER = EVENT_PACKAGE + "IEventMonitorListener";
+ /**
+ * Interfaces for handling download event information.
+ */
+ public static final String IEVENT_DOWNLOAD = EVENT_PACKAGE + "IEventDownload";
+ /**
+ * Interfaces for changes on downloading event information.
+ */
+ public static final String IEVENT_DOWNLOAD_LISTENER = EVENT_PACKAGE + "IEventDownloadListener";
+ /**
+ * Interfaces for handling download event information for DVB and DTMB.
+ */
+ public static final String IEVENT_DOWNLOAD_SESSION = EVENT_PACKAGE + "IEventDownloadSession";
+ /**
+ * Interfaces for handling analog color system.
+ */
+ public static final String IANALOG_ATTRIBUTE_INTERFACE = ANALOG_PACKAGE
+ + "IAnalogAttributeInterface";
+ /**
+ * Interfaces for monitoring channel tuned information.
+ */
+ public static final String ICHANNEL_TUNED_INTERFACE = TUNE_PACKAGE + "IChannelTunedInterface";
+ /**
+ * Interfaces for changes on channel tuned information.
+ */
+ public static final String ICHANNEL_TUNED_LISTENER = TUNE_PACKAGE + "IChannelTunedListener";
+ /**
+ * Interfaces for handling tuner frontend signal info.
+ */
+ public static final String ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE = SIGNAL_PACKAGE
+ + "ITunerFrontendSignalInfoInterface";
+ /**
+ * Interfaces for changes on tuner frontend signal info.
+ */
+ public static final String ITUNER_FRONTEND_SIGNAL_INFO_LISTENER = SIGNAL_PACKAGE
+ + "ITunerFrontendSignalInfoListener";
+ /**
+ * Interfaces for handling mux tune operations.
+ */
+ public static final String IMUX_TUNE_SESSION = TUNE_PACKAGE + "IMuxTuneSession";
+ /**
+ * Interfaces for initing mux tune session.
+ */
+ public static final String IMUX_TUNE = TUNE_PACKAGE + "IMuxTune";
+
+ // Set of standardized AIDL interface canonical names
+ private static final Set<String> sTisExtensions = new HashSet<>(Set.of(
+ ISCAN_INTERFACE,
+ ISCAN_SESSION,
+ ISCAN_LISTENER,
+ IHDPLUS_INFO,
+ IOPERATOR_DETECTION,
+ IOPERATOR_DETECTION_LISTENER,
+ IREGION_CHANNEL_LIST,
+ IREGION_CHANNEL_LIST_LISTENER,
+ ITARGET_REGION,
+ ITARGET_REGION_LISTENER,
+ ILCN_CONFLICT,
+ ILCN_CONFLICT_LISTENER,
+ ILCNV2_CHANNEL_LIST,
+ ILCNV2_CHANNEL_LIST_LISTENER,
+ IFAVORITE_NETWORK,
+ IFAVORITE_NETWORK_LISTENER,
+ ITKGS_INFO,
+ ITKGS_INFO_LISTENER,
+ ISCAN_SAT_SEARCH,
+ IOAD_UPDATE_INTERFACE,
+ ICAM_APP_INFO_SERVICE,
+ ICAM_APP_INFO_LISTENER,
+ ICAM_MONITORING_SERVICE,
+ ICAM_INFO_LISTENER,
+ ICI_OPERATOR_INTERFACE,
+ ICI_OPERATOR_LISTENER,
+ ICAM_PROFILE_INTERFACE,
+ ICONTENT_CONTROL_SERVICE,
+ ICAM_DRM_INFO_LISTENER,
+ ICAM_PIN_SERVICE,
+ ICAM_PIN_CAPABILITY_LISTENER,
+ ICAM_PIN_STATUS_LISTENER,
+ ICAM_HOST_CONTROL_SERVICE,
+ ICAM_HOST_CONTROL_ASK_RELEASE_REPLY_CALLBACK,
+ ICAM_HOST_CONTROL_INFO_LISTENER,
+ ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG,
+ ICAM_HOST_CONTROL_TUNE_QUIETLY_FLAG_LISTENER,
+ IMMI_INTERFACE,
+ IMMI_SESSION,
+ IMMI_STATUS_CALLBACK,
+ IENTER_MENU_ERROR_CALLBACK,
+ IDOWNLOADABLE_RATING_TABLE_MONITOR,
+ IRATING_INTERFACE,
+ IPMT_RATING_INTERFACE,
+ IPMT_RATING_LISTENER,
+ IVBI_RATING_INTERFACE,
+ IVBI_RATING_LISTENER,
+ IPROGRAM_INFO,
+ IPROGRAM_INFO_LISTENER,
+ BROADCAST_TIME,
+ IDATA_SERVICE_SIGNAL_INFO,
+ IDATA_SERVICE_SIGNAL_INFO_LISTENER,
+ ITELETEXT_PAGE_SUB_CODE,
+ ISCAN_BACKGROUND_SERVICE_UPDATE,
+ ISCAN_BACKGROUND_SERVICE_UPDATE_LISTENER,
+ ICLIENT_TOKEN,
+ ISCREEN_MODE_SETTINGS,
+ IHDMI_SIGNAL_INTERFACE,
+ IHDMI_SIGNAL_INFO_LISTENER,
+ IAUDIO_SIGNAL_INFO,
+ IANALOG_AUDIO_INFO,
+ IAUDIO_SIGNAL_INFO_LISTENER,
+ IVIDEO_SIGNAL_INFO,
+ IVIDEO_SIGNAL_INFO_LISTENER,
+ ISERVICE_LIST_EDIT,
+ ISERVICE_LIST_EDIT_LISTENER,
+ ISERVICE_LIST,
+ ISERVICE_LIST_TRANSFER_INTERFACE,
+ ISERVICE_LIST_EXPORT_SESSION,
+ ISERVICE_LIST_EXPORT_LISTENER,
+ ISERVICE_LIST_IMPORT_SESSION,
+ ISERVICE_LIST_IMPORT_LISTENER,
+ ISERVICE_LIST_SET_CHANNEL_LIST_SESSION,
+ ISERVICE_LIST_SET_CHANNEL_LIST_LISTENER,
+ ICHANNEL_LIST_TRANSFER,
+ IRECORDED_CONTENTS,
+ IDELETE_RECORDED_CONTENTS_CALLBACK,
+ IGET_INFO_RECORDED_CONTENTS_CALLBACK,
+ IEVENT_MONITOR,
+ IEVENT_MONITOR_LISTENER,
+ IEVENT_DOWNLOAD,
+ IEVENT_DOWNLOAD_LISTENER,
+ IEVENT_DOWNLOAD_SESSION,
+ IANALOG_ATTRIBUTE_INTERFACE,
+ ICHANNEL_TUNED_INTERFACE,
+ ICHANNEL_TUNED_LISTENER,
+ ITUNER_FRONTEND_SIGNAL_INFO_INTERFACE,
+ ITUNER_FRONTEND_SIGNAL_INFO_LISTENER,
+ IMUX_TUNE_SESSION,
+ IMUX_TUNE
+ ));
+
+ // Store the mapping between interface names and IBinder
+ private Map<String, IBinder> mExtensionInterfaceIBinderMapping = new HashMap<>();
+
+ TvInputServiceExtensionManager() {
+ }
+
+ /**
+ * Function to return available extension interface names
+ *
+ * @hide
+ */
+ public static @NonNull List<String> getStandardExtensionInterfaceNames() {
+ return new ArrayList<>(sTisExtensions);
+ }
+
+ /**
+ * Function to check if the extension is in the standardization list
+ */
+ static boolean checkIsStandardizedInterfaces(@NonNull String extensionName) {
+ return sTisExtensions.contains(extensionName);
+ }
+
+ /**
+ * This function should be used by OEM to register IBinder objects that implement
+ * standardized AIDL interfaces.
+ *
+ * @param extensionName Extension Interface Name
+ * @param binder IBinder object to be registered
+ * @return REGISTER_SUCCESS on success of registering IBinder object
+ * REGISTER_FAIL_NAME_NOT_STANDARDIZED on failure due to registering extension with
+ * non-standardized name
+ * REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED on failure due to IBinder not
+ * implementing standardized AIDL interface
+ * REGISTER_FAIL_REMOTE_EXCEPTION on failure due to remote exception
+ *
+ * @hide
+ */
+ public int registerExtensionIBinder(@NonNull String extensionName,
+ @NonNull IBinder binder) {
+ if (!checkIsStandardizedInterfaces(extensionName)) {
+ return REGISTER_FAIL_NAME_NOT_STANDARDIZED;
+ }
+ try {
+ if (binder.getInterfaceDescriptor().equals(extensionName)) {
+ mExtensionInterfaceIBinderMapping.put(extensionName, binder);
+ return REGISTER_SUCCESS;
+ } else {
+ return REGISTER_FAIL_IMPLEMENTATION_NOT_STANDARDIZED;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fetching IBinder object failure due to " + e);
+ return REGISTER_FAIL_REMOTE_EXCEPTION;
+ }
+ }
+
+ /**
+ * Function to get corresponding IBinder object
+ */
+ @Nullable IBinder getExtensionIBinder(@NonNull String extensionName) {
+ return mExtensionInterfaceIBinderMapping.get(extensionName);
+ }
+
+}
diff --git a/media/java/android/mtp/OWNERS b/media/java/android/mtp/OWNERS
index 6b5336e83bdc..77ed08b1f9a5 100644
--- a/media/java/android/mtp/OWNERS
+++ b/media/java/android/mtp/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com
diff --git a/media/tests/MtpTests/OWNERS b/media/tests/MtpTests/OWNERS
index 6b5336e83bdc..bdb6cdbea332 100644
--- a/media/tests/MtpTests/OWNERS
+++ b/media/tests/MtpTests/OWNERS
@@ -1,10 +1,9 @@
set noparent
-aprasath@google.com
anothermark@google.com
-kumarashishg@google.com
-sarup@google.com
+febinthattil@google.com
+aprasath@google.com
jsharkey@android.com
jameswei@google.com
rmojumder@google.com
-
+kumarashishg@google.com \ No newline at end of file
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 96b7c1339190..008120429c40 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -207,6 +207,7 @@ package android.nfc.cardemulation {
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method @FlaggedApi("android.nfc.enable_card_emulation_euicc") public boolean isEuiccSupported();
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void registerNfcEventListener(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopPatternFilterForService(@NonNull android.content.ComponentName, @NonNull String, boolean);
method public boolean removeAidsForService(android.content.ComponentName, String);
@@ -216,6 +217,7 @@ package android.nfc.cardemulation {
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
method @FlaggedApi("android.nfc.nfc_observe_mode") public boolean setShouldDefaultToObserveModeForService(@NonNull android.content.ComponentName, boolean);
method public boolean supportsAidPrefixRegistration();
+ method @FlaggedApi("android.nfc.nfc_event_listener") public void unregisterNfcEventListener(@NonNull android.nfc.cardemulation.CardEmulation.NfcEventListener);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean unsetOffHostForService(@NonNull android.content.ComponentName);
method public boolean unsetPreferredService(android.app.Activity);
field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
@@ -233,13 +235,16 @@ package android.nfc.cardemulation {
field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
}
+ @FlaggedApi("android.nfc.nfc_event_listener") public static interface CardEmulation.NfcEventListener {
+ method @FlaggedApi("android.nfc.nfc_event_listener") public default void onObserveModeStateChanged(boolean);
+ method @FlaggedApi("android.nfc.nfc_event_listener") public default void onPreferredServiceChanged(boolean);
+ }
+
public abstract class HostApduService extends android.app.Service {
ctor public HostApduService();
method public final void notifyUnhandled();
method public final android.os.IBinder onBind(android.content.Intent);
method public abstract void onDeactivated(int);
- method @FlaggedApi("android.nfc.nfc_event_listener") public void onObserveModeStateChanged(boolean);
- method @FlaggedApi("android.nfc.nfc_event_listener") public void onPreferredServiceChanged(boolean);
method public abstract byte[] processCommandApdu(byte[], android.os.Bundle);
method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void processPollingFrames(@NonNull java.util.List<android.nfc.cardemulation.PollingFrame>);
method public final void sendResponseApdu(byte[]);
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 24e14e69637b..6aa8a2b73f2c 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -91,6 +91,7 @@ package android.nfc {
method public void onDisable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onDisableFinished(int);
method public void onDisableStarted();
+ method public void onEeListenActivated(boolean);
method public void onEnable(@NonNull java.util.function.Consumer<java.lang.Boolean>);
method public void onEnableFinished(int);
method public void onEnableStarted();
@@ -105,7 +106,7 @@ package android.nfc {
method public void onRfFieldActivated(boolean);
method public void onRoutingChanged();
method public void onStateUpdated(int);
- method public void onTagConnected(boolean, @NonNull android.nfc.Tag);
+ method public void onTagConnected(boolean);
method public void onTagDispatch(@NonNull java.util.function.Consumer<java.lang.Boolean>);
}
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.aidl b/nfc/java/android/nfc/ComponentNameAndUser.aidl
new file mode 100644
index 000000000000..e677998a7970
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.nfc;
+
+parcelable ComponentNameAndUser; \ No newline at end of file
diff --git a/nfc/java/android/nfc/ComponentNameAndUser.java b/nfc/java/android/nfc/ComponentNameAndUser.java
new file mode 100644
index 000000000000..59e6c62926c9
--- /dev/null
+++ b/nfc/java/android/nfc/ComponentNameAndUser.java
@@ -0,0 +1,100 @@
+/*
+ * 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.nfc;
+
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public class ComponentNameAndUser implements Parcelable {
+ @UserIdInt private final int mUserId;
+ private ComponentName mComponentName;
+
+ public ComponentNameAndUser(@UserIdInt int userId, ComponentName componentName) {
+ mUserId = userId;
+ mComponentName = componentName;
+ }
+
+ /**
+ * @hide
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(mUserId);
+ out.writeParcelable(mComponentName, flags);
+ }
+
+ public static final Parcelable.Creator<ComponentNameAndUser> CREATOR =
+ new Parcelable.Creator<ComponentNameAndUser>() {
+ public ComponentNameAndUser createFromParcel(Parcel in) {
+ return new ComponentNameAndUser(in);
+ }
+
+ public ComponentNameAndUser[] newArray(int size) {
+ return new ComponentNameAndUser[size];
+ }
+ };
+
+ private ComponentNameAndUser(Parcel in) {
+ mUserId = in.readInt();
+ mComponentName = in.readParcelable(null, ComponentName.class);
+ }
+
+ @UserIdInt
+ public int getUserId() {
+ return mUserId;
+ }
+
+ public ComponentName getComponentName() {
+ return mComponentName;
+ }
+
+ @Override
+ public String toString() {
+ return mComponentName + " for user id: " + mUserId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj != null && obj instanceof ComponentNameAndUser) {
+ ComponentNameAndUser other = (ComponentNameAndUser) obj;
+ return other.getUserId() == mUserId
+ && Objects.equals(other.getComponentName(), mComponentName);
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ if (mComponentName == null) {
+ return mUserId;
+ }
+ return mComponentName.hashCode() + mUserId;
+ }
+}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index 8535e4a9cfd2..5e2e92d958a4 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -17,6 +17,8 @@
package android.nfc;
import android.content.ComponentName;
+import android.nfc.INfcEventListener;
+
import android.nfc.cardemulation.AidGroup;
import android.nfc.cardemulation.ApduServiceInfo;
import android.os.RemoteCallback;
@@ -55,4 +57,7 @@ interface INfcCardEmulation
boolean isAutoChangeEnabled();
List<String> getRoutingStatus();
void overwriteRoutingTable(int userHandle, String emptyAid, String protocol, String tech, String sc);
+
+ void registerNfcEventListener(in INfcEventListener listener);
+ void unregisterNfcEventListener(in INfcEventListener listener);
}
diff --git a/nfc/java/android/nfc/INfcEventListener.aidl b/nfc/java/android/nfc/INfcEventListener.aidl
new file mode 100644
index 000000000000..5162c26ac536
--- /dev/null
+++ b/nfc/java/android/nfc/INfcEventListener.aidl
@@ -0,0 +1,11 @@
+package android.nfc;
+
+import android.nfc.ComponentNameAndUser;
+
+/**
+ * @hide
+ */
+oneway interface INfcEventListener {
+ void onPreferredServiceChanged(in ComponentNameAndUser ComponentNameAndUser);
+ void onObserveModeStateChanged(boolean isEnabled);
+} \ No newline at end of file
diff --git a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
index 48c7ee659266..7f1fd15fe68a 100644
--- a/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
+++ b/nfc/java/android/nfc/INfcOemExtensionCallback.aidl
@@ -27,7 +27,7 @@ import java.util.List;
* @hide
*/
interface INfcOemExtensionCallback {
- void onTagConnected(boolean connected, in Tag tag);
+ void onTagConnected(boolean connected);
void onStateUpdated(int state);
void onApplyRouting(in ResultReceiver isSkipped);
void onNdefRead(in ResultReceiver isSkipped);
@@ -46,6 +46,7 @@ interface INfcOemExtensionCallback {
void onCardEmulationActivated(boolean isActivated);
void onRfFieldActivated(boolean isActivated);
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
+ void onEeListenActivated(boolean isActivated);
void onGetOemAppSearchIntent(in List<String> firstPackage, in ResultReceiver intentConsumer);
void onNdefMessage(in Tag tag, in NdefMessage message, in ResultReceiver hasOemExecutableContent);
void onLaunchHceAppChooserActivity(in String selectedAid, in List<ApduServiceInfo> services, in ComponentName failedComponent, in String category);
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 474ff8c663e6..1d2085c88213 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -80,6 +80,7 @@ public final class NfcOemExtension {
private boolean mCardEmulationActivated = false;
private boolean mRfFieldActivated = false;
private boolean mRfDiscoveryStarted = false;
+ private boolean mEeListenActivated = false;
/**
* Broadcast Action: Sent on NFC stack initialization when NFC OEM extensions are enabled.
@@ -195,9 +196,8 @@ public final class NfcOemExtension {
* ex - if tag is connected notify cover and Nfctest app if app is in testing mode
*
* @param connected status of the tag true if tag is connected otherwise false
- * @param tag Tag details
*/
- void onTagConnected(boolean connected, @NonNull Tag tag);
+ void onTagConnected(boolean connected);
/**
* Update the Nfc Adapter State
@@ -327,6 +327,13 @@ public final class NfcOemExtension {
void onRfDiscoveryStarted(boolean isDiscoveryStarted);
/**
+ * Notifies the NFCEE (NFC Execution Environment) Listen has been activated.
+ *
+ * @param isActivated true, if EE Listen is ON, else EE Listen is OFF.
+ */
+ void onEeListenActivated(boolean isActivated);
+
+ /**
* Gets the intent to find the OEM package in the OEM App market. If the consumer returns
* {@code null} or a timeout occurs, the intent from the first available package will be
* used instead.
@@ -437,6 +444,7 @@ public final class NfcOemExtension {
callback.onCardEmulationActivated(mCardEmulationActivated);
callback.onRfFieldActivated(mRfFieldActivated);
callback.onRfDiscoveryStarted(mRfDiscoveryStarted);
+ callback.onEeListenActivated(mEeListenActivated);
});
}
}
@@ -684,9 +692,9 @@ public final class NfcOemExtension {
private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
@Override
- public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
+ public void onTagConnected(boolean connected) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
- handleVoid2ArgCallback(connected, tag, cb::onTagConnected, ex));
+ handleVoidCallback(connected, cb::onTagConnected, ex));
}
@Override
@@ -711,6 +719,13 @@ public final class NfcOemExtension {
}
@Override
+ public void onEeListenActivated(boolean isActivated) throws RemoteException {
+ mEeListenActivated = isActivated;
+ mCallbackMap.forEach((cb, ex) ->
+ handleVoidCallback(isActivated, cb::onEeListenActivated, ex));
+ }
+
+ @Override
public void onStateUpdated(int state) throws RemoteException {
mCallbackMap.forEach((cb, ex) ->
handleVoidCallback(state, cb::onStateUpdated, ex));
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index d75318f53fe3..9ff83fe77c9b 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,10 +52,12 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TreeMap;
import java.util.regex.Pattern;
/**
@@ -204,7 +206,8 @@ public final class ApduServiceInfo implements Parcelable {
this(info, onHost, description, staticAidGroups, dynamicAidGroups,
requiresUnlock, requiresScreenOn, bannerResource, uid,
settingsActivityName, offHost, staticOffHost, isEnabled,
- new HashMap<String, Boolean>(), new HashMap<Pattern, Boolean>());
+ new HashMap<String, Boolean>(), new TreeMap<>(
+ Comparator.comparing(Pattern::toString)));
}
/**
@@ -340,7 +343,8 @@ public final class ApduServiceInfo implements Parcelable {
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
mAutoTransact = new HashMap<String, Boolean>();
- mAutoTransactPatterns = new HashMap<Pattern, Boolean>();
+ mAutoTransactPatterns = new TreeMap<Pattern, Boolean>(
+ Comparator.comparing(Pattern::toString));
mOnHost = onHost;
final int depth = parser.getDepth();
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index d8f04c50b695..eb28c3b9c930 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -17,6 +17,7 @@
package android.nfc.cardemulation;
import android.Manifest;
+import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -33,15 +34,18 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.nfc.ComponentNameAndUser;
import android.nfc.Constants;
import android.nfc.Flags;
import android.nfc.INfcCardEmulation;
+import android.nfc.INfcEventListener;
import android.nfc.NfcAdapter;
import android.os.Build;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
+import android.util.ArrayMap;
import android.util.Log;
import java.lang.annotation.Retention;
@@ -50,6 +54,8 @@ import java.util.HashMap;
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
+import java.util.concurrent.Executor;
import java.util.regex.Pattern;
/**
@@ -1076,4 +1082,107 @@ public final class CardEmulation {
default -> throw new IllegalStateException("Unexpected value: " + route);
};
}
+
+ /** Listener for preferred service state changes. */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public interface NfcEventListener {
+ /**
+ * This method is called when this package gains or loses preferred Nfc service status,
+ * either the Default Wallet Role holder (see {@link
+ * android.app.role.RoleManager#ROLE_WALLET}) or the preferred service of the foreground
+ * activity set with {@link #setPreferredService(Activity, ComponentName)}
+ *
+ * @param isPreferred true is this service has become the preferred Nfc service, false if it
+ * is no longer the preferred service
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ default void onPreferredServiceChanged(boolean isPreferred) {}
+
+ /**
+ * This method is called when observe mode has been enabled or disabled.
+ *
+ * @param isEnabled true if observe mode has been enabled, false if it has been disabled
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ default void onObserveModeStateChanged(boolean isEnabled) {}
+ }
+
+ private final ArrayMap<NfcEventListener, Executor> mNfcEventListeners = new ArrayMap<>();
+
+ final INfcEventListener mINfcEventListener =
+ new INfcEventListener.Stub() {
+ public void onPreferredServiceChanged(ComponentNameAndUser componentNameAndUser) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ boolean isPreferred =
+ componentNameAndUser != null
+ && componentNameAndUser.getUserId()
+ == mContext.getUser().getIdentifier()
+ && componentNameAndUser.getComponentName() != null
+ && Objects.equals(
+ mContext.getPackageName(),
+ componentNameAndUser.getComponentName()
+ .getPackageName());
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.forEach(
+ (listener, executor) -> {
+ executor.execute(
+ () -> listener.onPreferredServiceChanged(isPreferred));
+ });
+ }
+ }
+
+ public void onObserveModeStateChanged(boolean isEnabled) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.forEach(
+ (listener, executor) -> {
+ executor.execute(
+ () -> listener.onObserveModeStateChanged(isEnabled));
+ });
+ }
+ }
+ };
+
+ /**
+ * Register a listener for NFC Events.
+ *
+ * @param executor The Executor to run the call back with
+ * @param listener The listener to register
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public void registerNfcEventListener(
+ @NonNull @CallbackExecutor Executor executor, @NonNull NfcEventListener listener) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.put(listener, executor);
+ if (mNfcEventListeners.size() == 1) {
+ callService(() -> sService.registerNfcEventListener(mINfcEventListener));
+ }
+ }
+ }
+
+ /**
+ * Unregister a preferred service listener that was previously registered with {@link
+ * #registerNfcEventListener(Executor, NfcEventListener)}
+ *
+ * @param listener The previously registered listener to unregister
+ */
+ @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
+ public void unregisterNfcEventListener(@NonNull NfcEventListener listener) {
+ if (!android.nfc.Flags.nfcEventListener()) {
+ return;
+ }
+ synchronized (mNfcEventListeners) {
+ mNfcEventListeners.remove(listener);
+ if (mNfcEventListeners.size() == 0) {
+ callService(() -> sService.unregisterNfcEventListener(mINfcEventListener));
+ }
+ }
+ }
}
diff --git a/nfc/java/android/nfc/cardemulation/HostApduService.java b/nfc/java/android/nfc/cardemulation/HostApduService.java
index cd8e19c54565..4f601f0704b4 100644
--- a/nfc/java/android/nfc/cardemulation/HostApduService.java
+++ b/nfc/java/android/nfc/cardemulation/HostApduService.java
@@ -239,15 +239,6 @@ public abstract class HostApduService extends Service {
*/
public static final int MSG_POLLING_LOOP = 4;
- /**
- * @hide
- */
- public static final int MSG_OBSERVE_MODE_CHANGE = 5;
-
- /**
- * @hide
- */
- public static final int MSG_PREFERRED_SERVICE_CHANGED = 6;
/**
* @hide
@@ -343,16 +334,6 @@ public abstract class HostApduService extends Service {
processPollingFrames(pollingFrames);
}
break;
- case MSG_OBSERVE_MODE_CHANGE:
- if (android.nfc.Flags.nfcEventListener()) {
- onObserveModeStateChanged(msg.arg1 == 1);
- }
- break;
- case MSG_PREFERRED_SERVICE_CHANGED:
- if (android.nfc.Flags.nfcEventListener()) {
- onPreferredServiceChanged(msg.arg1 == 1);
- }
- break;
default:
super.handleMessage(msg);
}
@@ -462,25 +443,4 @@ public abstract class HostApduService extends Service {
*/
public abstract void onDeactivated(int reason);
-
- /**
- * This method is called when this service is the preferred Nfc service and
- * Observe mode has been enabled or disabled.
- *
- * @param isEnabled true if observe mode has been enabled, false if it has been disabled
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void onObserveModeStateChanged(boolean isEnabled) {
-
- }
-
- /**
- * This method is called when this service gains or loses preferred Nfc service status.
- *
- * @param isPreferred true is this service has become the preferred Nfc service,
- * false if it is no longer the preferred service
- */
- @FlaggedApi(android.nfc.Flags.FLAG_NFC_EVENT_LISTENER)
- public void onPreferredServiceChanged(boolean isPreferred) {
- }
}
diff --git a/packages/SettingsLib/Graph/graph.proto b/packages/SettingsLib/Graph/graph.proto
index cbe602e00406..6b93cd73164f 100644
--- a/packages/SettingsLib/Graph/graph.proto
+++ b/packages/SettingsLib/Graph/graph.proto
@@ -77,6 +77,8 @@ message PreferenceProto {
// Intent to show and locate the preference (might have highlight animation on
// the preference).
optional IntentProto launch_intent = 14;
+ // Descriptor of the preference value.
+ optional PreferenceValueDescriptorProto value_descriptor = 15;
// Target of an Intent
message ActionTarget {
@@ -103,9 +105,28 @@ message TextProto {
message PreferenceValueProto {
oneof value {
bool boolean_value = 1;
+ int32 int_value = 2;
}
}
+// Proto of preference value descriptor.
+message PreferenceValueDescriptorProto {
+ oneof type {
+ bool boolean_type = 1;
+ RangeValueProto range_value = 2;
+ }
+}
+
+// Proto of preference value that is between a range.
+message RangeValueProto {
+ // The lower bound (inclusive) of the range.
+ optional int32 min = 1;
+ // The upper bound (inclusive) of the range.
+ optional int32 max = 2;
+ // The increment step within the range. 0 means unset, which implies step size is 1.
+ optional int32 step = 3;
+}
+
// Proto of android.content.Intent
message IntentProto {
// The action of the Intent.
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 fdffe5de3f53..5ceee6d09978 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/GetPreferenceGraphApiHandler.kt
@@ -65,6 +65,7 @@ constructor(
val visitedScreens: Set<String> = setOf(),
val locale: Locale? = null,
val includeValue: Boolean = true,
+ val includeValueDescriptor: Boolean = true,
)
object GetPreferenceGraphRequestCodec : MessageCodec<GetPreferenceGraphRequest> {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
index cf6bf7012ac2..2256bb38dd2c 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceGraphBuilder.kt
@@ -49,6 +49,7 @@ import com.android.settingslib.metadata.PreferenceScreenMetadata
import com.android.settingslib.metadata.PreferenceScreenRegistry
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.preference.PreferenceScreenFactory
import com.android.settingslib.preference.PreferenceScreenProvider
import java.util.Locale
@@ -66,6 +67,7 @@ private constructor(private val context: Context, private val request: GetPrefer
private val builder by lazy { PreferenceGraphProto.newBuilder() }
private val visitedScreens = mutableSetOf<String>().apply { addAll(request.visitedScreens) }
private val includeValue = request.includeValue
+ private val includeValueDescriptor = request.includeValueDescriptor
private suspend fun init() {
for (key in request.screenKeys) {
@@ -284,14 +286,37 @@ private constructor(private val context: Context, private val request: GetPrefer
restricted = metadata.isRestricted(context)
}
persistent = metadata.isPersistent(context)
- if (
- includeValue &&
- persistent &&
- metadata is BooleanValue &&
- metadata is PersistentPreference<*>
- ) {
- metadata.storage(context).getValue(metadata.key, Boolean::class.javaObjectType)?.let {
- value = preferenceValueProto { booleanValue = it }
+ if (persistent) {
+ if (includeValue && metadata is PersistentPreference<*>) {
+ value = preferenceValueProto {
+ when (metadata) {
+ is BooleanValue ->
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Boolean::class.javaObjectType)
+ ?.let { booleanValue = it }
+ is RangeValue -> {
+ metadata
+ .storage(context)
+ .getValue(metadata.key, Int::class.javaObjectType)
+ ?.let { intValue = it }
+ }
+ else -> {}
+ }
+ }
+ }
+ if (includeValueDescriptor) {
+ valueDescriptor = preferenceValueDescriptorProto {
+ when (metadata) {
+ is BooleanValue -> booleanType = true
+ is RangeValue -> rangeValue = rangeValueProto {
+ min = metadata.getMinValue(context)
+ max = metadata.getMaxValue(context)
+ step = metadata.getIncrementStep(context)
+ }
+ else -> {}
+ }
+ }
}
}
if (metadata is PreferenceScreenMetadata) {
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
index d8db1bb776f5..6e4db1d90484 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/PreferenceSetterApi.kt
@@ -27,8 +27,10 @@ import com.android.settingslib.ipc.MessageCodec
import com.android.settingslib.metadata.BooleanValue
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
+import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.PreferenceRestrictionProvider
import com.android.settingslib.metadata.PreferenceScreenRegistry
+import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
/** Request to set preference value. */
@@ -114,27 +116,39 @@ class PreferenceSetterApiHandler(override val id: Int) : ApiHandler<PreferenceSe
if (metadata is PreferenceAvailabilityProvider && !metadata.isAvailable(application)) {
return PreferenceSetterResult.UNAVAILABLE
}
+
+ fun <T> PreferenceMetadata.checkWritePermit(value: T): Int {
+ @Suppress("UNCHECKED_CAST") val preference = (this as PersistentPreference<T>)
+ return when (preference.getWritePermit(application, value, myUid, callingUid)) {
+ ReadWritePermit.ALLOW -> PreferenceSetterResult.OK
+ ReadWritePermit.DISALLOW -> PreferenceSetterResult.DISALLOW
+ ReadWritePermit.REQUIRE_APP_PERMISSION ->
+ PreferenceSetterResult.REQUIRE_APP_PERMISSION
+ ReadWritePermit.REQUIRE_USER_AGREEMENT ->
+ PreferenceSetterResult.REQUIRE_USER_AGREEMENT
+ else -> PreferenceSetterResult.INTERNAL_ERROR
+ }
+ }
+
val storage = metadata.storage(application)
val value = request.value
try {
if (value.hasBooleanValue()) {
if (metadata !is BooleanValue) return PreferenceSetterResult.INVALID_REQUEST
val booleanValue = value.booleanValue
- @Suppress("UNCHECKED_CAST")
- val booleanPreference = metadata as PersistentPreference<Boolean>
- when (
- booleanPreference.getWritePermit(application, booleanValue, myUid, callingUid)
- ) {
- ReadWritePermit.ALLOW -> {}
- ReadWritePermit.DISALLOW -> return PreferenceSetterResult.DISALLOW
- ReadWritePermit.REQUIRE_APP_PERMISSION ->
- return PreferenceSetterResult.REQUIRE_APP_PERMISSION
- ReadWritePermit.REQUIRE_USER_AGREEMENT ->
- return PreferenceSetterResult.REQUIRE_USER_AGREEMENT
- else -> return PreferenceSetterResult.INTERNAL_ERROR
- }
+ val resultCode = metadata.checkWritePermit(booleanValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
storage.setValue(key, Boolean::class.javaObjectType, booleanValue)
return PreferenceSetterResult.OK
+ } else if (value.hasIntValue()) {
+ val intValue = value.intValue
+ val resultCode = metadata.checkWritePermit(intValue)
+ if (resultCode != PreferenceSetterResult.OK) return resultCode
+ if (metadata is RangeValue && !metadata.isValidValue(application, intValue)) {
+ return PreferenceSetterResult.INVALID_REQUEST
+ }
+ storage.setValue(key, Int::class.javaObjectType, intValue)
+ return PreferenceSetterResult.OK
}
} catch (e: Exception) {
return PreferenceSetterResult.INTERNAL_ERROR
diff --git a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
index d7dae7771acd..dee32d9ed80e 100644
--- a/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
+++ b/packages/SettingsLib/Graph/src/com/android/settingslib/graph/ProtoDsl.kt
@@ -24,7 +24,9 @@ import com.android.settingslib.graph.proto.PreferenceOrGroupProto
import com.android.settingslib.graph.proto.PreferenceProto
import com.android.settingslib.graph.proto.PreferenceProto.ActionTarget
import com.android.settingslib.graph.proto.PreferenceScreenProto
+import com.android.settingslib.graph.proto.PreferenceValueDescriptorProto
import com.android.settingslib.graph.proto.PreferenceValueProto
+import com.android.settingslib.graph.proto.RangeValueProto
import com.android.settingslib.graph.proto.TextProto
/** Returns root or null. */
@@ -89,6 +91,16 @@ inline fun actionTargetProto(init: ActionTarget.Builder.() -> Unit) =
inline fun preferenceValueProto(init: PreferenceValueProto.Builder.() -> Unit) =
PreferenceValueProto.newBuilder().also(init).build()
+/** Kotlin DSL-style builder for [PreferenceValueDescriptorProto]. */
+@JvmSynthetic
+inline fun preferenceValueDescriptorProto(init: PreferenceValueDescriptorProto.Builder.() -> Unit) =
+ PreferenceValueDescriptorProto.newBuilder().also(init).build()
+
+/** Kotlin DSL-style builder for [RangeValueProto]. */
+@JvmSynthetic
+inline fun rangeValueProto(init: RangeValueProto.Builder.() -> Unit) =
+ RangeValueProto.newBuilder().also(init).build()
+
/** Kotlin DSL-style builder for [TextProto]. */
@JvmSynthetic
inline fun textProto(init: TextProto.Builder.() -> Unit) = TextProto.newBuilder().also(init).build()
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
index cc42dab6737e..944bef6c9e09 100644
--- a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_preference_text_frame.xml
@@ -21,6 +21,7 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingVertical="@dimen/settingslib_expressive_space_small1"
+ android:paddingHorizontal="@dimen/settingslib_expressive_space_small1"
android:filterTouchesWhenObscured="false">
<TextView
@@ -40,7 +41,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@android:id/title"
- android:layout_alignLeft="@android:id/title"
android:layout_alignStart="@android:id/title"
android:layout_gravity="start"
android:textAlignment="viewStart"
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
index c3b1a7cb16e3..aeea8cb6df1b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiver.kt
@@ -24,6 +24,7 @@ import android.media.AudioManager
import android.util.Log
import com.android.settingslib.volume.shared.model.AudioManagerEvent
import com.android.settingslib.volume.shared.model.AudioStream
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharedFlow
@@ -31,6 +32,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
@@ -44,6 +46,7 @@ interface AudioManagerEventsReceiver {
class AudioManagerEventsReceiverImpl(
private val context: Context,
coroutineScope: CoroutineScope,
+ backgroundCoroutineContext: CoroutineContext,
) : AudioManagerEventsReceiver {
private val allActions: Collection<String>
@@ -79,6 +82,7 @@ class AudioManagerEventsReceiverImpl(
.filterNotNull()
.filter { intent -> allActions.contains(intent.action) }
.mapNotNull { it.toAudioManagerEvent() }
+ .flowOn(backgroundCoroutineContext)
.shareIn(coroutineScope, SharingStarted.WhileSubscribed())
private fun Intent.toAudioManagerEvent(): AudioManagerEvent? {
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
index 35ee8287d52f..58a09fbacc59 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/shared/AudioManagerEventsReceiverTest.kt
@@ -60,7 +60,12 @@ class AudioManagerEventsReceiverTest {
fun setup() {
MockitoAnnotations.initMocks(this)
- underTest = AudioManagerEventsReceiverImpl(context, testScope.backgroundScope)
+ underTest =
+ AudioManagerEventsReceiverImpl(
+ context,
+ testScope.backgroundScope,
+ testScope.testScheduler,
+ )
}
@Test
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index a18b6c1b301a..bffda8bcae65 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -536,6 +536,8 @@ android_library {
"androidx.room_room-runtime",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"com.google.android.material_material",
"device_state_flags_lib",
"kotlinx_coroutines_android",
@@ -703,6 +705,8 @@ android_library {
"androidx.room_room-testing",
"androidx.room_room-ktx",
"androidx.datastore_datastore-preferences",
+ "androidx.media3.media3-common",
+ "androidx.media3.media3-session",
"device_state_flags_lib",
"kotlinx-coroutines-android",
"kotlinx-coroutines-core",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index f0e1b437ec51..e0117368515b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -1013,6 +1013,7 @@
android:autoRemoveFromRecents="true"
android:launchMode="singleTop"
android:showForAllUsers="true"
+ android:turnScreenOn="true"
android:exported="false">
</activity>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 5c5edb1d00ba..3bf3e24a2ba6 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -26,6 +26,16 @@ flag {
}
flag {
+ name: "user_encrypted_source"
+ namespace: "systemui"
+ description: "Get rid of the local cache and rely on UserManager.isUserUnlocked directly to determine whether user CE storage is encrypted."
+ bug: "333656491"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "modes_ui_dialog_paging"
namespace: "systemui"
description: "Add pagination to the Modes dialog in quick settings."
@@ -468,6 +478,15 @@ flag {
}
flag {
+ name: "status_bar_notification_chips_test"
+ namespace: "systemui"
+ description: "Flag to enable certain features that let us test the status bar notification "
+ "chips with teamfooders. This flag should *never* be released to trunkfood or nextfood."
+ bug: "361346412"
+}
+
+
+flag {
name: "compose_bouncer"
namespace: "systemui"
description: "Use the new compose bouncer in SystemUI"
@@ -1515,6 +1534,16 @@ flag {
}
flag {
+ namespace: "systemui"
+ name: "user_aware_settings_repositories"
+ description: "Provide user-aware versions of SecureSettingsRepository and SystemSettingsRepository in SystemUI modules (see doc linked from b/356099784)."
+ bug: "356099784"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "notify_password_text_view_user_activity_in_background"
namespace: "systemui"
description: "Decide whether to notify the user activity in password text view, to power manager in the background thread."
@@ -1559,6 +1588,13 @@ flag {
}
flag {
+ name: "show_clipboard_indication"
+ namespace: "systemui"
+ description: "Show indication text under the clipboard overlay when copied something"
+ bug: "361199935"
+}
+
+flag {
name: "media_projection_dialog_behind_lockscreen"
namespace: "systemui"
description: "Ensure MediaProjection Dialog appears behind the lockscreen"
@@ -1707,4 +1743,3 @@ flag {
description: "An implementation of shortcut customizations through shortcut helper."
bug: "365064144"
}
-
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
index 4cf264253bf8..fdb4871423c3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt
@@ -20,6 +20,7 @@ import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
import android.content.Context
+import android.graphics.PointF
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.drawable.GradientDrawable
@@ -33,13 +34,13 @@ import android.view.ViewOverlay
import android.view.animation.Interpolator
import android.window.WindowAnimationState
import com.android.app.animation.Interpolators.LINEAR
-import com.android.app.animation.MathUtils.max
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.dynamicanimation.animation.SpringAnimation
import com.android.internal.dynamicanimation.animation.SpringForce
import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary
import java.util.concurrent.Executor
import kotlin.math.abs
+import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
@@ -91,6 +92,14 @@ class TransitionAnimator(
)
}
+ /**
+ * Similar to [getProgress] above, bug the delay and duration are expressed as percentages
+ * of the animation duration (between 0f and 1f).
+ */
+ internal fun getProgress(linearProgress: Float, delay: Float, duration: Float): Float {
+ return getProgressInternal(totalDuration = 1f, linearProgress, delay, duration)
+ }
+
private fun getProgressInternal(
totalDuration: Float,
linearProgress: Float,
@@ -262,10 +271,10 @@ class TransitionAnimator(
var centerY: Float,
var scale: Float = 0f,
- // Cached values.
- var previousCenterX: Float = -1f,
- var previousCenterY: Float = -1f,
- var previousScale: Float = -1f,
+ // Update flags (used to decide whether it's time to update the transition state).
+ var isCenterXUpdated: Boolean = false,
+ var isCenterYUpdated: Boolean = false,
+ var isScaleUpdated: Boolean = false,
// Completion flags.
var isCenterXDone: Boolean = false,
@@ -286,6 +295,7 @@ class TransitionAnimator(
override fun setValue(state: SpringState, value: Float) {
state.centerX = value
+ state.isCenterXUpdated = true
}
},
CENTER_Y {
@@ -295,6 +305,7 @@ class TransitionAnimator(
override fun setValue(state: SpringState, value: Float) {
state.centerY = value
+ state.isCenterYUpdated = true
}
},
SCALE {
@@ -304,6 +315,7 @@ class TransitionAnimator(
override fun setValue(state: SpringState, value: Float) {
state.scale = value
+ state.isScaleUpdated = true
}
};
@@ -444,8 +456,8 @@ class TransitionAnimator(
* punching a hole in the [transition container][Controller.transitionContainer]) iff [drawHole]
* is true.
*
- * If [useSpring] is true, a multi-spring animation will be used instead of the default
- * interpolators.
+ * If [startVelocity] (expressed in pixels per second) is not null, a multi-spring animation
+ * using it for the initial momentum will be used instead of the default interpolators.
*/
fun startAnimation(
controller: Controller,
@@ -453,9 +465,9 @@ class TransitionAnimator(
windowBackgroundColor: Int,
fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
- useSpring: Boolean = false,
+ startVelocity: PointF? = null,
): Animation {
- if (!controller.isLaunching || useSpring) checkReturnAnimationFrameworkFlag()
+ if (!controller.isLaunching || startVelocity != null) checkReturnAnimationFrameworkFlag()
// We add an extra layer with the same color as the dialog/app splash screen background
// color, which is usually the same color of the app background. We first fade in this layer
@@ -474,7 +486,7 @@ class TransitionAnimator(
windowBackgroundLayer,
fadeWindowBackgroundLayer,
drawHole,
- useSpring,
+ startVelocity,
)
.apply { start() }
}
@@ -487,7 +499,7 @@ class TransitionAnimator(
windowBackgroundLayer: GradientDrawable,
fadeWindowBackgroundLayer: Boolean = true,
drawHole: Boolean = false,
- useSpring: Boolean = false,
+ startVelocity: PointF? = null,
): Animation {
val transitionContainer = controller.transitionContainer
val transitionContainerOverlay = transitionContainer.overlay
@@ -504,11 +516,12 @@ class TransitionAnimator(
openingWindowSyncView != null &&
openingWindowSyncView.viewRootImpl != controller.transitionContainer.viewRootImpl
- return if (useSpring && springTimings != null && springInterpolators != null) {
+ return if (startVelocity != null && springTimings != null && springInterpolators != null) {
createSpringAnimation(
controller,
startState,
endState,
+ startVelocity,
windowBackgroundLayer,
transitionContainer,
transitionContainerOverlay,
@@ -693,6 +706,7 @@ class TransitionAnimator(
controller: Controller,
startState: State,
endState: State,
+ startVelocity: PointF,
windowBackgroundLayer: GradientDrawable,
transitionContainer: View,
transitionContainerOverlay: ViewGroupOverlay,
@@ -721,19 +735,20 @@ class TransitionAnimator(
fun updateProgress(state: SpringState) {
if (
- (!state.isCenterXDone && state.centerX == state.previousCenterX) ||
- (!state.isCenterYDone && state.centerY == state.previousCenterY) ||
- (!state.isScaleDone && state.scale == state.previousScale)
+ !(state.isCenterXUpdated || state.isCenterXDone) ||
+ !(state.isCenterYUpdated || state.isCenterYDone) ||
+ !(state.isScaleUpdated || state.isScaleDone)
) {
// Because all three springs use the same update method, we only actually update
- // when all values have changed, avoiding two redundant calls per frame.
+ // when all properties have received their new value (which could be unchanged from
+ // the previous one), avoiding two redundant calls per frame.
return
}
- // Update the latest values for the check above.
- state.previousCenterX = state.centerX
- state.previousCenterY = state.centerY
- state.previousScale = state.scale
+ // Reset the update flags.
+ state.isCenterXUpdated = false
+ state.isCenterYUpdated = false
+ state.isScaleUpdated = false
// Current scale-based values, that will be used to find the new animation bounds.
val width =
@@ -829,6 +844,7 @@ class TransitionAnimator(
}
setStartValue(startState.centerX)
+ setStartVelocity(startVelocity.x)
setMinValue(min(startState.centerX, endState.centerX))
setMaxValue(max(startState.centerX, endState.centerX))
@@ -850,6 +866,7 @@ class TransitionAnimator(
}
setStartValue(startState.centerY)
+ setStartVelocity(startVelocity.y)
setMinValue(min(startState.centerY, endState.centerY))
setMaxValue(max(startState.centerY, endState.centerY))
@@ -1057,15 +1074,13 @@ class TransitionAnimator(
interpolators = springInterpolators!!
val timings = springTimings!!
fadeInProgress =
- getProgressInternal(
- totalDuration = 1f,
+ getProgress(
linearProgress,
timings.contentBeforeFadeOutDelay,
timings.contentBeforeFadeOutDuration,
)
fadeOutProgress =
- getProgressInternal(
- totalDuration = 1f,
+ getProgress(
linearProgress,
timings.contentAfterFadeInDelay,
timings.contentAfterFadeInDuration,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 6fc51e4d0f65..e78862e0e922 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,6 +53,7 @@ import com.android.systemui.notifications.ui.composable.ConstrainedNotificationS
import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.res.R
import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -84,7 +85,7 @@ constructor(
stackScrollLayout: NotificationStackScrollLayout,
sharedNotificationContainerBinder: SharedNotificationContainerBinder,
private val keyguardRootViewModel: KeyguardRootViewModel,
- private val configurationState: ConfigurationState,
+ @ShadeDisplayAware private val configurationState: ConfigurationState,
private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -127,7 +128,7 @@ constructor(
}
val burnIn = rememberBurnIn(clockInteractor)
AnimatedVisibility(
- visibleState = transitionState,
+ visibleState = transitionState,
enter = fadeIn(),
exit = fadeOut(),
modifier =
@@ -150,7 +151,7 @@ constructor(
)
}
}
- },
+ }
)
}
}
@@ -172,7 +173,7 @@ constructor(
areNotificationsVisible: Boolean,
isShadeLayoutWide: Boolean,
burnInParams: BurnInParameters?,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
) {
if (!areNotificationsVisible) {
return
@@ -192,10 +193,7 @@ constructor(
if (burnInParams == null) {
it
} else {
- it.burnInAware(
- viewModel = aodBurnInViewModel,
- params = burnInParams,
- )
+ it.burnInAware(viewModel = aodBurnInViewModel, params = burnInParams)
}
},
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
index 2a91bd8b1d73..26c827a5417c 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt
@@ -43,6 +43,7 @@ import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.lifecycle.rememberViewModel
+import com.android.systemui.notifications.ui.composable.SnoozeableHeadsUpNotificationSpace
import com.android.systemui.qs.composefragment.ui.GridAnchor
import com.android.systemui.qs.panels.ui.compose.EditMode
import com.android.systemui.qs.panels.ui.compose.TileGrid
@@ -53,8 +54,11 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.ExpandedShadeHeader
import com.android.systemui.shade.ui.composable.OverlayShade
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -67,6 +71,8 @@ constructor(
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
+ private val notificationStackScrollView: Lazy<NotificationScrollView>,
+ private val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
) : Overlay {
override val key = Overlays.QuickSettingsShade
@@ -98,6 +104,14 @@ constructor(
ShadeBody(viewModel = viewModel.quickSettingsContainerViewModel)
}
+
+ SnoozeableHeadsUpNotificationSpace(
+ stackScrollView = notificationStackScrollView.get(),
+ viewModel =
+ rememberViewModel("QuickSettingsShadeOverlay") {
+ notificationsPlaceholderViewModelFactory.create()
+ },
+ )
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
index 7872ffad6cec..041cd15bdeea 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt
@@ -36,12 +36,11 @@ internal typealias SuspendedValue<T> = suspend () -> T
internal interface DraggableHandler {
/**
- * Start a drag in the given [startedPosition], with the given [overSlop] and number of
- * [pointersDown].
+ * Start a drag with the given [pointersInfo] and [overSlop].
*
* The returned [DragController] should be used to continue or stop the drag.
*/
- fun onDragStarted(startedPosition: Offset?, overSlop: Float, pointersDown: Int): DragController
+ fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController
}
/**
@@ -96,7 +95,7 @@ internal class DraggableHandlerImpl(
* Note: if this returns true, then [onDragStarted] will be called with overSlop equal to 0f,
* indicating that the transition should be intercepted.
*/
- internal fun shouldImmediatelyIntercept(startedPosition: Offset?): Boolean {
+ internal fun shouldImmediatelyIntercept(pointersInfo: PointersInfo?): Boolean {
// We don't intercept the touch if we are not currently driving the transition.
val dragController = dragController
if (dragController?.isDrivingTransition != true) {
@@ -107,7 +106,7 @@ internal class DraggableHandlerImpl(
// Only intercept the current transition if one of the 2 swipes results is also a transition
// between the same pair of contents.
- val swipes = computeSwipes(startedPosition, pointersDown = 1)
+ val swipes = computeSwipes(pointersInfo)
val fromContent = layoutImpl.content(swipeAnimation.currentContent)
val (upOrLeft, downOrRight) = swipes.computeSwipesResults(fromContent)
val currentScene = layoutImpl.state.currentScene
@@ -124,11 +123,7 @@ internal class DraggableHandlerImpl(
))
}
- override fun onDragStarted(
- startedPosition: Offset?,
- overSlop: Float,
- pointersDown: Int,
- ): DragController {
+ override fun onDragStarted(pointersInfo: PointersInfo?, overSlop: Float): DragController {
if (overSlop == 0f) {
val oldDragController = dragController
check(oldDragController != null && oldDragController.isDrivingTransition) {
@@ -153,7 +148,7 @@ internal class DraggableHandlerImpl(
return updateDragController(swipes, swipeAnimation)
}
- val swipes = computeSwipes(startedPosition, pointersDown)
+ val swipes = computeSwipes(pointersInfo)
val fromContent = layoutImpl.contentForUserActions()
swipes.updateSwipesResults(fromContent)
@@ -190,8 +185,7 @@ internal class DraggableHandlerImpl(
return createSwipeAnimation(layoutImpl, result, isUpOrLeft, orientation)
}
- internal fun resolveSwipeSource(startedPosition: Offset?): SwipeSource.Resolved? {
- if (startedPosition == null) return null
+ internal fun resolveSwipeSource(startedPosition: Offset): SwipeSource.Resolved? {
return layoutImpl.swipeSourceDetector.source(
layoutSize = layoutImpl.lastSize,
position = startedPosition.round(),
@@ -200,57 +194,44 @@ internal class DraggableHandlerImpl(
)
}
- internal fun resolveSwipe(
- pointersDown: Int,
- fromSource: SwipeSource.Resolved?,
- isUpOrLeft: Boolean,
- ): Swipe.Resolved {
- return Swipe.Resolved(
- direction =
- when (orientation) {
- Orientation.Horizontal ->
- if (isUpOrLeft) {
- SwipeDirection.Resolved.Left
- } else {
- SwipeDirection.Resolved.Right
- }
-
- Orientation.Vertical ->
- if (isUpOrLeft) {
- SwipeDirection.Resolved.Up
- } else {
- SwipeDirection.Resolved.Down
- }
- },
- pointerCount = pointersDown,
- fromSource = fromSource,
+ private fun computeSwipes(pointersInfo: PointersInfo?): Swipes {
+ val fromSource = pointersInfo?.let { resolveSwipeSource(it.startedPosition) }
+ return Swipes(
+ upOrLeft = resolveSwipe(orientation, isUpOrLeft = true, pointersInfo, fromSource),
+ downOrRight = resolveSwipe(orientation, isUpOrLeft = false, pointersInfo, fromSource),
)
}
+}
- private fun computeSwipes(startedPosition: Offset?, pointersDown: Int): Swipes {
- val fromSource = resolveSwipeSource(startedPosition)
- val upOrLeft = resolveSwipe(pointersDown, fromSource, isUpOrLeft = true)
- val downOrRight = resolveSwipe(pointersDown, fromSource, isUpOrLeft = false)
- return if (fromSource == null) {
- Swipes(
- upOrLeft = null,
- downOrRight = null,
- upOrLeftNoSource = upOrLeft,
- downOrRightNoSource = downOrRight,
- )
- } else {
- Swipes(
- upOrLeft = upOrLeft,
- downOrRight = downOrRight,
- upOrLeftNoSource = upOrLeft.copy(fromSource = null),
- downOrRightNoSource = downOrRight.copy(fromSource = null),
- )
- }
- }
+private fun resolveSwipe(
+ orientation: Orientation,
+ isUpOrLeft: Boolean,
+ pointersInfo: PointersInfo?,
+ fromSource: SwipeSource.Resolved?,
+): Swipe.Resolved {
+ return Swipe.Resolved(
+ direction =
+ when (orientation) {
+ Orientation.Horizontal ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Left
+ } else {
+ SwipeDirection.Resolved.Right
+ }
- companion object {
- private const val TAG = "DraggableHandlerImpl"
- }
+ Orientation.Vertical ->
+ if (isUpOrLeft) {
+ SwipeDirection.Resolved.Up
+ } else {
+ SwipeDirection.Resolved.Down
+ }
+ },
+ // If the number of pointers is not specified, 1 is assumed.
+ pointerCount = pointersInfo?.pointersDown ?: 1,
+ // Resolves the pointer type only if all pointers are of the same type.
+ pointersType = pointersInfo?.pointersDownByType?.keys?.singleOrNull(),
+ fromSource = fromSource,
+ )
}
/** @param swipes The [Swipes] associated to the current gesture. */
@@ -498,24 +479,14 @@ private class DragControllerImpl(
}
/** The [Swipe] associated to a given fromScene, startedPosition and pointersDown. */
-internal class Swipes(
- val upOrLeft: Swipe.Resolved?,
- val downOrRight: Swipe.Resolved?,
- val upOrLeftNoSource: Swipe.Resolved?,
- val downOrRightNoSource: Swipe.Resolved?,
-) {
+internal class Swipes(val upOrLeft: Swipe.Resolved, val downOrRight: Swipe.Resolved) {
/** The [UserActionResult] associated to up and down swipes. */
var upOrLeftResult: UserActionResult? = null
var downOrRightResult: UserActionResult? = null
fun computeSwipesResults(fromContent: Content): Pair<UserActionResult?, UserActionResult?> {
- val userActions = fromContent.userActions
- fun result(swipe: Swipe.Resolved?): UserActionResult? {
- return userActions[swipe ?: return null]
- }
-
- val upOrLeftResult = result(upOrLeft) ?: result(upOrLeftNoSource)
- val downOrRightResult = result(downOrRight) ?: result(downOrRightNoSource)
+ val upOrLeftResult = fromContent.findActionResultBestMatch(swipe = upOrLeft)
+ val downOrRightResult = fromContent.findActionResultBestMatch(swipe = downOrRight)
return upOrLeftResult to downOrRightResult
}
@@ -569,11 +540,13 @@ internal class NestedScrollHandlerImpl(
val connection: PriorityNestedScrollConnection = nestedScrollConnection()
- private fun PointersInfo.resolveSwipe(isUpOrLeft: Boolean): Swipe.Resolved {
- return draggableHandler.resolveSwipe(
- pointersDown = pointersDown,
- fromSource = draggableHandler.resolveSwipeSource(startedPosition),
+ private fun resolveSwipe(isUpOrLeft: Boolean, pointersInfo: PointersInfo?): Swipe.Resolved {
+ return resolveSwipe(
+ orientation = draggableHandler.orientation,
isUpOrLeft = isUpOrLeft,
+ pointersInfo = pointersInfo,
+ fromSource =
+ pointersInfo?.let { draggableHandler.resolveSwipeSource(it.startedPosition) },
)
}
@@ -582,12 +555,7 @@ internal class NestedScrollHandlerImpl(
// moving on to the next scene.
var canChangeScene = false
- var _lastPointersInfo: PointersInfo? = null
- fun pointersInfo(): PointersInfo {
- return checkNotNull(_lastPointersInfo) {
- "PointersInfo should be initialized before the transition begins."
- }
- }
+ var lastPointersInfo: PointersInfo? = null
fun hasNextScene(amount: Float): Boolean {
val transitionState = layoutState.transitionState
@@ -595,17 +563,11 @@ internal class NestedScrollHandlerImpl(
val fromScene = layoutImpl.scene(scene)
val resolvedSwipe =
when {
- amount < 0f -> pointersInfo().resolveSwipe(isUpOrLeft = true)
- amount > 0f -> pointersInfo().resolveSwipe(isUpOrLeft = false)
+ amount < 0f -> resolveSwipe(isUpOrLeft = true, lastPointersInfo)
+ amount > 0f -> resolveSwipe(isUpOrLeft = false, lastPointersInfo)
else -> null
}
- val nextScene =
- resolvedSwipe?.let {
- fromScene.userActions[it]
- ?: if (it.fromSource != null) {
- fromScene.userActions[it.copy(fromSource = null)]
- } else null
- }
+ val nextScene = resolvedSwipe?.let { fromScene.findActionResultBestMatch(it) }
if (nextScene != null) return true
if (transitionState !is TransitionState.Idle) return false
@@ -619,13 +581,14 @@ internal class NestedScrollHandlerImpl(
return PriorityNestedScrollConnection(
orientation = orientation,
canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ ->
+ val pointersInfo = pointersInfoOwner.pointersInfo()
canChangeScene =
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
val canInterceptSwipeTransition =
canChangeScene &&
offsetAvailable != 0f &&
- draggableHandler.shouldImmediatelyIntercept(startedPosition = null)
+ draggableHandler.shouldImmediatelyIntercept(pointersInfo)
if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
val threshold = layoutImpl.transitionInterceptionThreshold
@@ -636,13 +599,11 @@ internal class NestedScrollHandlerImpl(
return@PriorityNestedScrollConnection false
}
- val pointersInfo = pointersInfoOwner.pointersInfo()
-
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
// If the current swipe transition is *not* closed to 0f or 1f, then we want the
// scroll events to intercept the current transition to continue the scene
@@ -662,11 +623,11 @@ internal class NestedScrollHandlerImpl(
if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
val canStart =
when (behavior) {
@@ -704,11 +665,11 @@ internal class NestedScrollHandlerImpl(
canChangeScene = false
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return@PriorityNestedScrollConnection false
}
- _lastPointersInfo = pointersInfo
+ lastPointersInfo = pointersInfo
val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable)
if (canStart) {
@@ -718,12 +679,11 @@ internal class NestedScrollHandlerImpl(
canStart
},
onStart = { firstScroll ->
- val pointersInfo = pointersInfo()
+ val pointersInfo = lastPointersInfo
scrollController(
dragController =
draggableHandler.onDragStarted(
- pointersDown = pointersInfo.pointersDown,
- startedPosition = pointersInfo.startedPosition,
+ pointersInfo = pointersInfo,
overSlop = if (isIntercepting) 0f else firstScroll,
),
canChangeScene = canChangeScene,
@@ -742,7 +702,7 @@ private fun scrollController(
return object : ScrollController {
override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
val pointersInfo = pointersInfoOwner.pointersInfo()
- if (pointersInfo.isMouseWheel) {
+ if (pointersInfo?.isMouseWheel == true) {
// Do not support mouse wheel interactions
return 0f
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 63c5d7aed3e1..e7b66c5f0d2f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -52,6 +52,7 @@ import com.android.compose.animation.scene.content.Content
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.PropertyTransformation
import com.android.compose.animation.scene.transformation.SharedElementTransformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.drawInContainer
import com.android.compose.ui.util.lerp
@@ -187,6 +188,7 @@ private fun Modifier.maybeElevateInContent(
state.transformationSpec
.transformations(key, content.key)
.shared
+ ?.transformation
?.elevateInContent == content.key &&
isSharedElement(stateByContent, state) &&
isSharedElementEnabled(key, state) &&
@@ -901,7 +903,7 @@ private fun shouldPlaceElement(
}
val sharedTransformation = sharedElementTransformation(element.key, transition)
- if (sharedTransformation?.enabled == false) {
+ if (sharedTransformation?.transformation?.enabled == false) {
return true
}
@@ -954,13 +956,13 @@ private fun isSharedElementEnabled(
element: ElementKey,
transition: TransitionState.Transition,
): Boolean {
- return sharedElementTransformation(element, transition)?.enabled ?: true
+ return sharedElementTransformation(element, transition)?.transformation?.enabled ?: true
}
internal fun sharedElementTransformation(
element: ElementKey,
transition: TransitionState.Transition,
-): SharedElementTransformation? {
+): TransformationWithRange<SharedElementTransformation>? {
val transformationSpec = transition.transformationSpec
val sharedInFromContent =
transformationSpec.transformations(element, transition.fromContent).shared
@@ -1244,7 +1246,7 @@ private inline fun <T> computeValue(
element: Element,
transition: TransitionState.Transition?,
contentValue: (Element.State) -> T,
- transformation: (ElementTransformations) -> PropertyTransformation<T>?,
+ transformation: (ElementTransformations) -> TransformationWithRange<PropertyTransformation<T>>?,
currentValue: () -> T,
isSpecified: (T) -> Boolean,
lerp: (T, T, Float) -> T,
@@ -1280,7 +1282,7 @@ private inline fun <T> computeValue(
checkNotNull(if (currentContent == toContent) toState else fromState)
val idleValue = contentValue(overscrollState)
val targetValue =
- with(propertySpec) {
+ with(propertySpec.transformation) {
layoutImpl.propertyTransformationScope.transform(
currentContent,
element.key,
@@ -1375,7 +1377,7 @@ private inline fun <T> computeValue(
val idleValue = contentValue(contentState)
val isEntering = content == toContent
val previewTargetValue =
- with(previewTransformation) {
+ with(previewTransformation.transformation) {
layoutImpl.propertyTransformationScope.transform(
content,
element.key,
@@ -1386,7 +1388,7 @@ private inline fun <T> computeValue(
val targetValueOrNull =
transformation?.let { transformation ->
- with(transformation) {
+ with(transformation.transformation) {
layoutImpl.propertyTransformationScope.transform(
content,
element.key,
@@ -1461,7 +1463,7 @@ private inline fun <T> computeValue(
val idleValue = contentValue(contentState)
val targetValue =
- with(transformation) {
+ with(transformation.transformation) {
layoutImpl.propertyTransformationScope.transform(
content,
element.key,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 8613f6da0f62..ab2324a87d81 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -33,6 +33,7 @@ import androidx.compose.ui.input.pointer.PointerEventType
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.changedToDown
import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
@@ -52,6 +53,7 @@ import androidx.compose.ui.util.fastAll
import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastFilter
import androidx.compose.ui.util.fastFirstOrNull
+import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastSumBy
import com.android.compose.ui.util.SpaceVectorConverter
import kotlin.coroutines.cancellation.CancellationException
@@ -78,8 +80,8 @@ import kotlinx.coroutines.launch
@Stable
internal fun Modifier.multiPointerDraggable(
orientation: Orientation,
- startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
onFirstPointerDown: () -> Unit = {},
swipeDetector: SwipeDetector = DefaultSwipeDetector,
dispatcher: NestedScrollDispatcher,
@@ -97,9 +99,8 @@ internal fun Modifier.multiPointerDraggable(
private data class MultiPointerDraggableElement(
private val orientation: Orientation,
- private val startDragImmediately: (startedPosition: Offset) -> Boolean,
- private val onDragStarted:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ private val startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ private val onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
private val onFirstPointerDown: () -> Unit,
private val swipeDetector: SwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -125,9 +126,8 @@ private data class MultiPointerDraggableElement(
internal class MultiPointerDraggableNode(
orientation: Orientation,
- var startDragImmediately: (startedPosition: Offset) -> Boolean,
- var onDragStarted:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ var startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ var onDragStarted: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
var onFirstPointerDown: () -> Unit,
swipeDetector: SwipeDetector = DefaultSwipeDetector,
private val dispatcher: NestedScrollDispatcher,
@@ -183,17 +183,22 @@ internal class MultiPointerDraggableNode(
pointerInput.onPointerEvent(pointerEvent, pass, bounds)
}
+ private var lastPointerEvent: PointerEvent? = null
private var startedPosition: Offset? = null
private var pointersDown: Int = 0
- private var isMouseWheel: Boolean = false
- internal fun pointersInfo(): PointersInfo {
- return PointersInfo(
+ internal fun pointersInfo(): PointersInfo? {
+ val startedPosition = startedPosition
+ val lastPointerEvent = lastPointerEvent
+ if (startedPosition == null || lastPointerEvent == null) {
// This may be null, i.e. when the user uses TalkBack
+ return null
+ }
+
+ return PointersInfo(
startedPosition = startedPosition,
- // We could have 0 pointers during fling or for other reasons.
- pointersDown = pointersDown.coerceAtLeast(1),
- isMouseWheel = isMouseWheel,
+ pointersDown = pointersDown,
+ lastPointerEvent = lastPointerEvent,
)
}
@@ -212,8 +217,8 @@ internal class MultiPointerDraggableNode(
if (pointerEvent.type == PointerEventType.Enter) continue
val changes = pointerEvent.changes
+ lastPointerEvent = pointerEvent
pointersDown = changes.countDown()
- isMouseWheel = pointerEvent.type == PointerEventType.Scroll
when {
// There are no more pointers down.
@@ -285,8 +290,8 @@ internal class MultiPointerDraggableNode(
detectDragGestures(
orientation = orientation,
startDragImmediately = startDragImmediately,
- onDragStart = { startedPosition, overSlop, pointersDown ->
- onDragStarted(startedPosition, overSlop, pointersDown)
+ onDragStart = { pointersInfo, overSlop ->
+ onDragStarted(pointersInfo, overSlop)
},
onDrag = { controller, amount ->
dispatchScrollEvents(
@@ -435,9 +440,8 @@ internal class MultiPointerDraggableNode(
*/
private suspend fun AwaitPointerEventScope.detectDragGestures(
orientation: Orientation,
- startDragImmediately: (startedPosition: Offset) -> Boolean,
- onDragStart:
- (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ startDragImmediately: (pointersInfo: PointersInfo) -> Boolean,
+ onDragStart: (pointersInfo: PointersInfo, overSlop: Float) -> DragController,
onDrag: (controller: DragController, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
onDragCancel: (controller: DragController) -> Unit,
@@ -462,8 +466,13 @@ internal class MultiPointerDraggableNode(
.first()
var overSlop = 0f
+ var lastPointersInfo =
+ checkNotNull(pointersInfo()) {
+ "We should have pointers down, last event: $currentEvent"
+ }
+
val drag =
- if (startDragImmediately(consumablePointer.position)) {
+ if (startDragImmediately(lastPointersInfo)) {
consumablePointer.consume()
consumablePointer
} else {
@@ -488,14 +497,18 @@ internal class MultiPointerDraggableNode(
consumablePointer.id,
onSlopReached,
)
- }
+ } ?: return
+ lastPointersInfo =
+ checkNotNull(pointersInfo()) {
+ "We should have pointers down, last event: $currentEvent"
+ }
// Make sure that overSlop is not 0f. This can happen when the user drags by exactly
// the touch slop. However, the overSlop we pass to onDragStarted() is used to
// compute the direction we are dragging in, so overSlop should never be 0f unless
// we intercept an ongoing swipe transition (i.e. startDragImmediately() returned
// true).
- if (drag != null && overSlop == 0f) {
+ if (overSlop == 0f) {
val delta = (drag.position - consumablePointer.position).toFloat()
check(delta != 0f) { "delta is equal to 0" }
overSlop = delta.sign
@@ -503,49 +516,38 @@ internal class MultiPointerDraggableNode(
drag
}
- if (drag != null) {
- val controller =
- onDragStart(
- // The startedPosition is the starting position when a gesture begins (when the
- // first pointer touches the screen), not the point where we begin dragging.
- // For example, this could be different if one of our children intercepts the
- // gesture first and then we do.
- requireNotNull(startedPosition),
- overSlop,
- pointersDown,
+ val controller = onDragStart(lastPointersInfo, overSlop)
+
+ val successful: Boolean
+ try {
+ onDrag(controller, overSlop)
+
+ successful =
+ drag(
+ initialPointerId = drag.id,
+ hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
+ onDrag = {
+ onDrag(controller, it.positionChange().toFloat())
+ it.consume()
+ },
+ onIgnoredEvent = {
+ // We are still dragging an object, but this event is not of interest to the
+ // caller.
+ // This event will not trigger the onDrag event, but we will consume the
+ // event to prevent another pointerInput from interrupting the current
+ // gesture just because the event was ignored.
+ it.consume()
+ },
)
+ } catch (t: Throwable) {
+ onDragCancel(controller)
+ throw t
+ }
- val successful: Boolean
- try {
- onDrag(controller, overSlop)
-
- successful =
- drag(
- initialPointerId = drag.id,
- hasDragged = { it.positionChangeIgnoreConsumed().toFloat() != 0f },
- onDrag = {
- onDrag(controller, it.positionChange().toFloat())
- it.consume()
- },
- onIgnoredEvent = {
- // We are still dragging an object, but this event is not of interest to
- // the caller.
- // This event will not trigger the onDrag event, but we will consume the
- // event to prevent another pointerInput from interrupting the current
- // gesture just because the event was ignored.
- it.consume()
- },
- )
- } catch (t: Throwable) {
- onDragCancel(controller)
- throw t
- }
-
- if (successful) {
- onDragEnd(controller)
- } else {
- onDragCancel(controller)
- }
+ if (successful) {
+ onDragEnd(controller)
+ } else {
+ onDragCancel(controller)
}
}
@@ -655,11 +657,57 @@ internal class MultiPointerDraggableNode(
}
internal fun interface PointersInfoOwner {
- fun pointersInfo(): PointersInfo
+ /**
+ * Provides information about the pointers interacting with this composable.
+ *
+ * @return A [PointersInfo] object containing details about the pointers, including the starting
+ * position and the number of pointers down, or `null` if there are no pointers down.
+ */
+ fun pointersInfo(): PointersInfo?
}
+/**
+ * Holds information about pointer interactions within a composable.
+ *
+ * This class stores details such as the starting position of a gesture, the number of pointers
+ * down, and whether the last pointer event was a mouse wheel scroll.
+ *
+ * @param startedPosition The starting position of the gesture. This is the position where the first
+ * pointer touched the screen, not necessarily the point where dragging begins. This may be
+ * different from the initial touch position if a child composable intercepts the gesture before
+ * this one.
+ * @param pointersDown The number of pointers currently down.
+ * @param isMouseWheel Indicates whether the last pointer event was a mouse wheel scroll.
+ * @param pointersDownByType Provide a map of pointer types to the count of pointers of that type
+ * currently down/pressed.
+ */
internal data class PointersInfo(
- val startedPosition: Offset?,
+ val startedPosition: Offset,
val pointersDown: Int,
val isMouseWheel: Boolean,
-)
+ val pointersDownByType: Map<PointerType, Int>,
+) {
+ init {
+ check(pointersDown > 0) { "We should have at least 1 pointer down, $pointersDown instead" }
+ }
+}
+
+private fun PointersInfo(
+ startedPosition: Offset,
+ pointersDown: Int,
+ lastPointerEvent: PointerEvent,
+): PointersInfo {
+ return PointersInfo(
+ startedPosition = startedPosition,
+ pointersDown = pointersDown,
+ isMouseWheel = lastPointerEvent.type == PointerEventType.Scroll,
+ pointersDownByType =
+ buildMap {
+ lastPointerEvent.changes.fastForEach { change ->
+ if (!change.pressed) return@fastForEach
+ val newValue = (get(change.type) ?: 0) + 1
+ put(change.type, newValue)
+ }
+ },
+ )
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 077927dfe0a2..5bf77ae9b23c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -233,6 +233,12 @@ sealed interface ObservableTransitionState {
(to == null || this.toContent == to)
}
+ fun isTransitioningSets(from: Set<ContentKey>? = null, to: Set<ContentKey>? = null): Boolean {
+ return this is Transition &&
+ (from == null || from.contains(this.fromContent)) &&
+ (to == null || to.contains(this.toContent))
+ }
+
/** Whether we are transitioning from [content] to [other], or from [other] to [content]. */
fun isTransitioningBetween(content: ContentKey, other: ContentKey): Boolean {
return isTransitioning(from = content, to = other) ||
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 504240390674..21d87e173728 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
@@ -27,6 +27,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
@@ -407,6 +408,7 @@ data object Back : UserAction() {
data class Swipe(
val direction: SwipeDirection,
val pointerCount: Int = 1,
+ val pointersType: PointerType? = null,
val fromSource: SwipeSource? = null,
) : UserAction() {
companion object {
@@ -422,6 +424,7 @@ data class Swipe(
return Resolved(
direction = direction.resolve(layoutDirection),
pointerCount = pointerCount,
+ pointersType = pointersType,
fromSource = fromSource?.resolve(layoutDirection),
)
}
@@ -431,6 +434,7 @@ data class Swipe(
val direction: SwipeDirection.Resolved,
val pointerCount: Int,
val fromSource: SwipeSource.Resolved?,
+ val pointersType: PointerType?,
) : UserAction.Resolved()
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index e1e2411da080..61332b61ed1b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -764,7 +764,8 @@ internal class MutableSceneTransitionLayoutStateImpl(
return@fastForEach
}
- state.transformationSpec.transformations.fastForEach { transformation ->
+ state.transformationSpec.transformations.fastForEach { transformationWithRange ->
+ val transformation = transformationWithRange.transformation
if (
transformation is SharedElementTransformation &&
transformation.elevateInContent != null
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index 8866fbfbf194..b083f79aebf5 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -33,10 +33,10 @@ import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
import com.android.compose.animation.scene.transformation.OverscrollTranslate
import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
+import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.animation.scene.transformation.Translate
/** The transitions configuration of a [SceneTransitionLayout]. */
@@ -233,7 +233,7 @@ interface TransformationSpec {
val distance: UserActionDistance?
/** The list of [Transformation] applied to elements during this transition. */
- val transformations: List<Transformation>
+ val transformations: List<TransformationWithRange<*>>
companion object {
internal val Empty =
@@ -325,7 +325,7 @@ internal class TransformationSpecImpl(
override val progressSpec: AnimationSpec<Float>,
override val swipeSpec: SpringSpec<Float>?,
override val distance: UserActionDistance?,
- override val transformations: List<Transformation>,
+ override val transformations: List<TransformationWithRange<*>>,
) : TransformationSpec {
private val cache = mutableMapOf<ElementKey, MutableMap<ContentKey, ElementTransformations>>()
@@ -340,59 +340,65 @@ internal class TransformationSpecImpl(
element: ElementKey,
content: ContentKey,
): ElementTransformations {
- var shared: SharedElementTransformation? = null
- var offset: PropertyTransformation<Offset>? = null
- var size: PropertyTransformation<IntSize>? = null
- var drawScale: PropertyTransformation<Scale>? = null
- var alpha: PropertyTransformation<Float>? = null
-
- fun <T> onPropertyTransformation(
- root: PropertyTransformation<T>,
- current: PropertyTransformation<T> = root,
- ) {
- when (current) {
+ var shared: TransformationWithRange<SharedElementTransformation>? = null
+ var offset: TransformationWithRange<PropertyTransformation<Offset>>? = null
+ var size: TransformationWithRange<PropertyTransformation<IntSize>>? = null
+ var drawScale: TransformationWithRange<PropertyTransformation<Scale>>? = null
+ var alpha: TransformationWithRange<PropertyTransformation<Float>>? = null
+
+ transformations.fastForEach { transformationWithRange ->
+ val transformation = transformationWithRange.transformation
+ if (!transformation.matcher.matches(element, content)) {
+ return@fastForEach
+ }
+
+ when (transformation) {
+ is SharedElementTransformation -> {
+ throwIfNotNull(shared, element, name = "shared")
+ shared =
+ transformationWithRange
+ as TransformationWithRange<SharedElementTransformation>
+ }
is Translate,
is OverscrollTranslate,
is EdgeTranslate,
is AnchoredTranslate -> {
throwIfNotNull(offset, element, name = "offset")
- offset = root as PropertyTransformation<Offset>
+ offset =
+ transformationWithRange
+ as TransformationWithRange<PropertyTransformation<Offset>>
}
is ScaleSize,
is AnchoredSize -> {
throwIfNotNull(size, element, name = "size")
- size = root as PropertyTransformation<IntSize>
+ size =
+ transformationWithRange
+ as TransformationWithRange<PropertyTransformation<IntSize>>
}
is DrawScale -> {
throwIfNotNull(drawScale, element, name = "drawScale")
- drawScale = root as PropertyTransformation<Scale>
+ drawScale =
+ transformationWithRange
+ as TransformationWithRange<PropertyTransformation<Scale>>
}
is Fade -> {
throwIfNotNull(alpha, element, name = "alpha")
- alpha = root as PropertyTransformation<Float>
- }
- is RangedPropertyTransformation -> onPropertyTransformation(root, current.delegate)
- }
- }
-
- transformations.fastForEach { transformation ->
- if (!transformation.matcher.matches(element, content)) {
- return@fastForEach
- }
-
- when (transformation) {
- is SharedElementTransformation -> {
- throwIfNotNull(shared, element, name = "shared")
- shared = transformation
+ alpha =
+ transformationWithRange
+ as TransformationWithRange<PropertyTransformation<Float>>
}
- is PropertyTransformation<*> -> onPropertyTransformation(transformation)
+ else -> error("Unknown transformation: $transformation")
}
}
return ElementTransformations(shared, offset, size, drawScale, alpha)
}
- private fun throwIfNotNull(previous: Transformation?, element: ElementKey, name: String) {
+ private fun throwIfNotNull(
+ previous: TransformationWithRange<*>?,
+ element: ElementKey,
+ name: String,
+ ) {
if (previous != null) {
error("$element has multiple $name transformations")
}
@@ -401,9 +407,9 @@ internal class TransformationSpecImpl(
/** The transformations of an element during a transition. */
internal class ElementTransformations(
- val shared: SharedElementTransformation?,
- val offset: PropertyTransformation<Offset>?,
- val size: PropertyTransformation<IntSize>?,
- val drawScale: PropertyTransformation<Scale>?,
- val alpha: PropertyTransformation<Float>?,
+ val shared: TransformationWithRange<SharedElementTransformation>?,
+ val offset: TransformationWithRange<PropertyTransformation<Offset>>?,
+ val size: TransformationWithRange<PropertyTransformation<IntSize>>?,
+ val drawScale: TransformationWithRange<PropertyTransformation<Scale>>?,
+ val alpha: TransformationWithRange<PropertyTransformation<Float>>?,
)
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 fdf01cce396b..ba5f4144aff9 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
@@ -19,7 +19,6 @@ package com.android.compose.animation.scene
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
import androidx.compose.ui.input.nestedscroll.nestedScrollModifierNode
import androidx.compose.ui.input.pointer.PointerEvent
@@ -65,6 +64,52 @@ private fun Content.shouldEnableSwipes(orientation: Orientation): Boolean {
return userActions.keys.any { it is Swipe.Resolved && it.direction.orientation == orientation }
}
+/**
+ * Finds the best matching [UserActionResult] for the given [swipe] within this [Content].
+ * Prioritizes actions with matching [Swipe.Resolved.fromSource].
+ *
+ * @param swipe The swipe to match against.
+ * @return The best matching [UserActionResult], or `null` if no match is found.
+ */
+internal fun Content.findActionResultBestMatch(swipe: Swipe.Resolved): UserActionResult? {
+ var bestPoints = Int.MIN_VALUE
+ var bestMatch: UserActionResult? = null
+ userActions.forEach { (actionSwipe, actionResult) ->
+ if (
+ actionSwipe !is Swipe.Resolved ||
+ // The direction must match.
+ actionSwipe.direction != swipe.direction ||
+ // The number of pointers down must match.
+ actionSwipe.pointerCount != swipe.pointerCount ||
+ // The action requires a specific fromSource.
+ (actionSwipe.fromSource != null && actionSwipe.fromSource != swipe.fromSource) ||
+ // The action requires a specific pointerType.
+ (actionSwipe.pointersType != null && actionSwipe.pointersType != swipe.pointersType)
+ ) {
+ // This action is not eligible.
+ return@forEach
+ }
+
+ val sameFromSource = actionSwipe.fromSource == swipe.fromSource
+ val samePointerType = actionSwipe.pointersType == swipe.pointersType
+ // Prioritize actions with a perfect match.
+ if (sameFromSource && samePointerType) {
+ return actionResult
+ }
+
+ var points = 0
+ if (sameFromSource) points++
+ if (samePointerType) points++
+
+ // Otherwise, keep track of the best eligible action.
+ if (points > bestPoints) {
+ bestPoints = points
+ bestMatch = actionResult
+ }
+ }
+ return bestMatch
+}
+
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
val swipeDetector: SwipeDetector,
@@ -155,10 +200,10 @@ private class SwipeToSceneNode(
override fun onCancelPointerInput() = multiPointerDraggableNode.onCancelPointerInput()
- private fun startDragImmediately(startedPosition: Offset): Boolean {
+ private fun startDragImmediately(pointersInfo: PointersInfo): Boolean {
// Immediately start the drag if the user can't swipe in the other direction and the gesture
// handler can intercept it.
- return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(startedPosition)
+ return !canOppositeSwipe() && draggableHandler.shouldImmediatelyIntercept(pointersInfo)
}
private fun canOppositeSwipe(): Boolean {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 269d91b02e7d..e461f9ccc295 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -34,12 +34,11 @@ import com.android.compose.animation.scene.transformation.DrawScale
import com.android.compose.animation.scene.transformation.EdgeTranslate
import com.android.compose.animation.scene.transformation.Fade
import com.android.compose.animation.scene.transformation.OverscrollTranslate
-import com.android.compose.animation.scene.transformation.PropertyTransformation
-import com.android.compose.animation.scene.transformation.RangedPropertyTransformation
import com.android.compose.animation.scene.transformation.ScaleSize
import com.android.compose.animation.scene.transformation.SharedElementTransformation
import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.animation.scene.transformation.Translate
internal fun transitionsImpl(builder: SceneTransitionsBuilder.() -> Unit): SceneTransitions {
@@ -158,7 +157,7 @@ private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
}
internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
- val transformations = mutableListOf<Transformation>()
+ val transformations = mutableListOf<TransformationWithRange<*>>()
private var range: TransformationRange? = null
protected var reversed = false
override var distance: UserActionDistance? = null
@@ -174,19 +173,13 @@ internal abstract class BaseTransitionBuilderImpl : BaseTransitionBuilder {
range = null
}
- protected fun transformation(transformation: PropertyTransformation<*>) {
- val transformation =
- if (range != null) {
- RangedPropertyTransformation(transformation, range!!)
- } else {
- transformation
- }
-
+ protected fun transformation(transformation: Transformation) {
+ val transformationWithRange = TransformationWithRange(transformation, range)
transformations.add(
if (reversed) {
- transformation.reversed()
+ transformationWithRange.reversed()
} else {
- transformation
+ transformationWithRange
}
)
}
@@ -264,7 +257,7 @@ internal class TransitionBuilderImpl(override val transition: TransitionState.Tr
"(${transition.toContent.debugName})"
}
- transformations.add(SharedElementTransformation(matcher, enabled, elevateInContent))
+ transformation(SharedElementTransformation(matcher, enabled, elevateInContent))
}
override fun timestampRange(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 5936d2595465..0ddeb7c7445f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -33,7 +33,7 @@ internal class AnchoredSize(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: IntSize,
+ idleValue: IntSize,
): IntSize {
fun anchorSizeIn(content: ContentKey): IntSize {
val size =
@@ -45,8 +45,8 @@ internal class AnchoredSize(
)
return IntSize(
- width = if (anchorWidth) size.width else value.width,
- height = if (anchorHeight) size.height else value.height,
+ width = if (anchorWidth) size.width else idleValue.width,
+ height = if (anchorHeight) size.height else idleValue.height,
)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index 0a59dfe515fc..47508b41633c 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -31,7 +31,7 @@ internal class AnchoredTranslate(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: Offset,
+ idleValue: Offset,
): Offset {
fun throwException(content: ContentKey?): Nothing {
throwMissingAnchorException(
@@ -51,9 +51,9 @@ internal class AnchoredTranslate(
val offset = anchorToOffset - anchorFromOffset
return if (content == transition.toContent) {
- Offset(value.x - offset.x, value.y - offset.y)
+ Offset(idleValue.x - offset.x, idleValue.y - offset.y)
} else {
- Offset(value.x + offset.x, value.y + offset.y)
+ Offset(idleValue.x + offset.x, idleValue.y + offset.y)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index 7223dad43a2e..8488ae5178b0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -33,12 +33,11 @@ internal class DrawScale(
private val scaleY: Float,
private val pivot: Offset = Offset.Unspecified,
) : PropertyTransformation<Scale> {
-
override fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: Scale,
+ idleValue: Scale,
): Scale {
return Scale(scaleX, scaleY, pivot)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 4ae07c541011..884aae4b8b1a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -33,37 +33,37 @@ internal class EdgeTranslate(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: Offset,
+ idleValue: Offset,
): Offset {
val sceneSize =
content.targetSize()
?: error("Content ${content.debugName} does not have a target size")
- val elementSize = element.targetSize(content) ?: return value
+ val elementSize = element.targetSize(content) ?: return idleValue
return when (edge.resolve(layoutDirection)) {
Edge.Resolved.Top ->
if (startsOutsideLayoutBounds) {
- Offset(value.x, -elementSize.height.toFloat())
+ Offset(idleValue.x, -elementSize.height.toFloat())
} else {
- Offset(value.x, 0f)
+ Offset(idleValue.x, 0f)
}
Edge.Resolved.Left ->
if (startsOutsideLayoutBounds) {
- Offset(-elementSize.width.toFloat(), value.y)
+ Offset(-elementSize.width.toFloat(), idleValue.y)
} else {
- Offset(0f, value.y)
+ Offset(0f, idleValue.y)
}
Edge.Resolved.Bottom ->
if (startsOutsideLayoutBounds) {
- Offset(value.x, sceneSize.height.toFloat())
+ Offset(idleValue.x, sceneSize.height.toFloat())
} else {
- Offset(value.x, (sceneSize.height - elementSize.height).toFloat())
+ Offset(idleValue.x, (sceneSize.height - elementSize.height).toFloat())
}
Edge.Resolved.Right ->
if (startsOutsideLayoutBounds) {
- Offset(sceneSize.width.toFloat(), value.y)
+ Offset(sceneSize.width.toFloat(), idleValue.y)
} else {
- Offset((sceneSize.width - elementSize.width).toFloat(), value.y)
+ Offset((sceneSize.width - elementSize.width).toFloat(), idleValue.y)
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index c11ec977fe2b..ef769e7d0c19 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -27,7 +27,7 @@ internal class Fade(override val matcher: ElementMatcher) : PropertyTransformati
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: Float,
+ idleValue: Float,
): Float {
// Return the alpha value of [element] either when it starts fading in or when it finished
// fading out, which is `0` in both cases.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index a159a5b5b2bd..ef3654b65b0a 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -36,11 +36,11 @@ internal class ScaleSize(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: IntSize,
+ idleValue: IntSize,
): IntSize {
return IntSize(
- width = (value.width * width).roundToInt(),
- height = (value.height * height).roundToInt(),
+ width = (idleValue.width * width).roundToInt(),
+ height = (idleValue.height * height).roundToInt(),
)
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index d38067d9af38..74a3ead3fbd7 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -36,14 +36,6 @@ sealed interface Transformation {
*/
val matcher: ElementMatcher
- /**
- * The range during which the transformation is applied. If it is `null`, then the
- * transformation will be applied throughout the whole scene transition.
- */
- // TODO(b/240432457): Move this back to PropertyTransformation.
- val range: TransformationRange?
- get() = null
-
/*
* Reverse this transformation. This is called when we use Transition(from = A, to = B) when
* animating from B to A and there is no Transition(from = B, to = A) defined.
@@ -66,14 +58,14 @@ interface PropertyTransformation<T> : Transformation {
* - the value at progress = 100% for elements that are leaving the layout (i.e. elements in the
* content we are transitioning from).
*
- * The returned value will be interpolated using the [transition] progress and [value], the
+ * The returned value will be interpolated using the [transition] progress and [idleValue], the
* value of the property when we are idle.
*/
fun PropertyTransformationScope.transform(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: T,
+ idleValue: T,
): T
}
@@ -82,20 +74,15 @@ interface PropertyTransformationScope : Density, ElementStateScope {
val layoutDirection: LayoutDirection
}
-/**
- * A [PropertyTransformation] associated to a range. This is a helper class so that normal
- * implementations of [PropertyTransformation] don't have to take care of reversing their range when
- * they are reversed.
- */
-internal class RangedPropertyTransformation<T>(
- val delegate: PropertyTransformation<T>,
- override val range: TransformationRange,
-) : PropertyTransformation<T> by delegate {
- override fun reversed(): Transformation {
- return RangedPropertyTransformation(
- delegate.reversed() as PropertyTransformation<T>,
- range.reversed(),
- )
+/** A pair consisting of a [transformation] and optional [range]. */
+class TransformationWithRange<out T : Transformation>(
+ val transformation: T,
+ val range: TransformationRange?,
+) {
+ fun reversed(): TransformationWithRange<T> {
+ if (range == null) return this
+
+ return TransformationWithRange(transformation = transformation, range = range.reversed())
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index af0a6edfa2fb..356ed9969458 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -35,9 +35,9 @@ internal class Translate(
content: ContentKey,
element: ElementKey,
transition: TransitionState.Transition,
- value: Offset,
+ idleValue: Offset,
): Offset {
- return Offset(value.x + x.toPx(), value.y + y.toPx())
+ return Offset(idleValue.x + x.toPx(), idleValue.y + y.toPx())
}
}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
index f24d93f0d79d..5dad0d75cfc5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt
@@ -23,6 +23,7 @@ import androidx.compose.material3.Text
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.UserInput
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.LayoutDirection
@@ -51,6 +52,20 @@ import org.junit.runner.RunWith
private const val SCREEN_SIZE = 100f
private val LAYOUT_SIZE = IntSize(SCREEN_SIZE.toInt(), SCREEN_SIZE.toInt())
+private fun pointersInfo(
+ startedPosition: Offset = Offset.Zero,
+ pointersDown: Int = 1,
+ isMouseWheel: Boolean = false,
+ pointersDownByType: Map<PointerType, Int> = mapOf(PointerType.Touch to pointersDown),
+): PointersInfo {
+ return PointersInfo(
+ startedPosition = startedPosition,
+ pointersDown = pointersDown,
+ isMouseWheel = isMouseWheel,
+ pointersDownByType = pointersDownByType,
+ )
+}
+
@RunWith(AndroidJUnit4::class)
class DraggableHandlerTest {
private class TestGestureScope(val testScope: MonotonicClockTestScope) {
@@ -126,9 +141,7 @@ class DraggableHandlerTest {
val draggableHandler = layoutImpl.draggableHandler(Orientation.Vertical)
val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal)
- var pointerInfoOwner: () -> PointersInfo = {
- PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false)
- }
+ var pointerInfoOwner: () -> PointersInfo = { pointersInfo() }
fun nestedScrollConnection(
nestedScrollBehavior: NestedScrollBehavior,
@@ -211,42 +224,32 @@ class DraggableHandlerTest {
}
fun onDragStarted(
- startedPosition: Offset = Offset.Zero,
+ pointersInfo: PointersInfo = pointersInfo(),
overSlop: Float,
- pointersDown: Int = 1,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
// overSlop should be 0f only if the drag gesture starts with startDragImmediately
if (overSlop == 0f) error("Consider using onDragStartedImmediately()")
return onDragStarted(
draggableHandler = draggableHandler,
- startedPosition = startedPosition,
+ pointersInfo = pointersInfo,
overSlop = overSlop,
- pointersDown = pointersDown,
expectedConsumedOverSlop = expectedConsumedOverSlop,
)
}
- fun onDragStartedImmediately(
- startedPosition: Offset = Offset.Zero,
- pointersDown: Int = 1,
- ): DragController {
- return onDragStarted(draggableHandler, startedPosition, overSlop = 0f, pointersDown)
+ fun onDragStartedImmediately(pointersInfo: PointersInfo = pointersInfo()): DragController {
+ return onDragStarted(draggableHandler, pointersInfo, overSlop = 0f)
}
fun onDragStarted(
draggableHandler: DraggableHandler,
- startedPosition: Offset = Offset.Zero,
+ pointersInfo: PointersInfo = pointersInfo(),
overSlop: Float = 0f,
- pointersDown: Int = 1,
expectedConsumedOverSlop: Float = overSlop,
): DragController {
val dragController =
- draggableHandler.onDragStarted(
- startedPosition = startedPosition,
- overSlop = overSlop,
- pointersDown = pointersDown,
- )
+ draggableHandler.onDragStarted(pointersInfo = pointersInfo, overSlop = overSlop)
// MultiPointerDraggable will always call onDelta with the initial overSlop right after
dragController.onDragDelta(pixels = overSlop, expectedConsumedOverSlop)
@@ -528,7 +531,8 @@ class DraggableHandlerTest {
mapOf(Swipe.Up to UserActionResult(SceneB, isIrreversible = true), Swipe.Down to SceneC)
val dragController =
onDragStarted(
- startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f),
+ pointersInfo =
+ pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE * 0.5f)),
overSlop = up(fractionOfScreen = 0.2f),
)
assertTransition(
@@ -554,7 +558,7 @@ class DraggableHandlerTest {
// Start dragging from the bottom
onDragStarted(
- startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE),
+ pointersInfo = pointersInfo(startedPosition = Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)),
overSlop = up(fractionOfScreen = 0.1f),
)
assertTransition(
@@ -1051,8 +1055,8 @@ class DraggableHandlerTest {
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
fromScene = SceneC,
@@ -1067,7 +1071,7 @@ class DraggableHandlerTest {
// should intercept it. Because it is intercepted, the overSlop passed to onDragStarted()
// should be 0f.
assertThat(draggableHandler.shouldImmediatelyIntercept(middle)).isTrue()
- onDragStartedImmediately(startedPosition = middle)
+ onDragStartedImmediately(pointersInfo = middle)
// We should have intercepted the transition, so the transition should be the same object.
assertTransition(
@@ -1083,9 +1087,9 @@ class DraggableHandlerTest {
// Start a new gesture from the bottom of the screen. Because swiping up from the bottom of
// C leads to scene A (and not B), the previous transitions is *not* intercepted and we
// instead animate from C to A.
- val bottom = Offset(SCREEN_SIZE / 2, SCREEN_SIZE)
+ val bottom = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2, SCREEN_SIZE))
assertThat(draggableHandler.shouldImmediatelyIntercept(bottom)).isFalse()
- onDragStarted(startedPosition = bottom, overSlop = up(0.1f))
+ onDragStarted(pointersInfo = bottom, overSlop = up(0.1f))
assertTransition(
currentScene = SceneC,
@@ -1102,8 +1106,8 @@ class DraggableHandlerTest {
navigateToSceneC()
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneC, toScene = SceneB, isUserInputOngoing = true)
// The current transition can be intercepted.
@@ -1119,15 +1123,15 @@ class DraggableHandlerTest {
@Test
fun interruptedTransitionCanNotBeImmediatelyIntercepted() = runGestureTest {
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
onDragStarted(overSlop = up(0.1f))
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
layoutState.startTransitionImmediately(
animationScope = testScope.backgroundScope,
transition(SceneA, SceneB),
)
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isFalse()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isFalse()
}
@Test
@@ -1159,7 +1163,7 @@ class DraggableHandlerTest {
assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
// Intercept the transition and swipe down back to scene A.
- assertThat(draggableHandler.shouldImmediatelyIntercept(startedPosition = null)).isTrue()
+ assertThat(draggableHandler.shouldImmediatelyIntercept(pointersInfo = null)).isTrue()
val dragController2 = onDragStartedImmediately()
// Block the transition when the user release their finger.
@@ -1203,9 +1207,7 @@ class DraggableHandlerTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Drag from the **top** of the screen
- pointerInfoOwner = {
- PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false)
- }
+ pointerInfoOwner = { pointersInfo() }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1222,13 +1224,7 @@ class DraggableHandlerTest {
advanceUntilIdle()
// Drag from the **bottom** of the screen
- pointerInfoOwner = {
- PointersInfo(
- startedPosition = Offset(0f, SCREEN_SIZE),
- pointersDown = 1,
- isMouseWheel = false,
- )
- }
+ pointerInfoOwner = { pointersInfo(startedPosition = Offset(0f, SCREEN_SIZE)) }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1248,9 +1244,7 @@ class DraggableHandlerTest {
val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways)
// Use mouse wheel
- pointerInfoOwner = {
- PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true)
- }
+ pointerInfoOwner = { pointersInfo(isMouseWheel = true) }
assertIdle(currentScene = SceneC)
nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f))
@@ -1260,8 +1254,8 @@ class DraggableHandlerTest {
@Test
fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest {
// Swipe up from the middle to transition to scene B.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.1f))
assertTransition(fromScene = SceneA, toScene = SceneB, isUserInputOngoing = true)
dragController.onDragStoppedAnimateLater(velocity = 0f)
@@ -1274,10 +1268,10 @@ class DraggableHandlerTest {
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
val dragController =
onDragStarted(
- startedPosition = middle,
+ pointersInfo = middle,
overSlop = up(2f),
// Overscroll is disabled, it will scroll up to 100%
expectedConsumedOverSlop = up(1f),
@@ -1305,8 +1299,8 @@ class DraggableHandlerTest {
layoutState.transitions = transitions { overscrollDisabled(SceneB, Orientation.Vertical) }
// Swipe up to scene B at progress = 200%.
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.99f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.99f))
assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.99f)
// Release the finger.
@@ -1351,9 +1345,9 @@ class DraggableHandlerTest {
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(0.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1383,9 +1377,9 @@ class DraggableHandlerTest {
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(0.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(0.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
@@ -1414,9 +1408,9 @@ class DraggableHandlerTest {
overscroll(SceneB, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(1.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1446,9 +1440,9 @@ class DraggableHandlerTest {
overscroll(SceneC, Orientation.Vertical) { fade(TestElements.Foo) }
}
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(1.5f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1.5f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
@@ -1480,8 +1474,8 @@ class DraggableHandlerTest {
mutableUserActionsA = mapOf(Swipe.Up to UserActionResult(SceneB))
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = down(1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = down(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneB)
@@ -1513,8 +1507,8 @@ class DraggableHandlerTest {
mutableUserActionsA = mapOf(Swipe.Down to UserActionResult(SceneC))
- val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f)
- val dragController = onDragStarted(startedPosition = middle, overSlop = up(1f))
+ val middle = pointersInfo(startedPosition = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f))
+ val dragController = onDragStarted(pointersInfo = middle, overSlop = up(1f))
val transition = assertThat(transitionState).isSceneTransition()
assertThat(transition).hasFromScene(SceneA)
assertThat(transition).hasToScene(SceneC)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index 3df608717414..5ec74f8d2260 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -98,7 +98,7 @@ class MultiPointerDraggableTest {
Modifier.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -167,7 +167,7 @@ class MultiPointerDraggableTest {
orientation = Orientation.Vertical,
// We want to start a drag gesture immediately
startDragImmediately = { true },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -239,7 +239,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -358,7 +358,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { dragged = true },
@@ -463,7 +463,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
verticalStarted = true
SimpleDragController(
onDrag = { verticalDragged = true },
@@ -475,7 +475,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Horizontal,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
horizontalStarted = true
SimpleDragController(
onDrag = { horizontalDragged = true },
@@ -574,7 +574,7 @@ class MultiPointerDraggableTest {
return swipeConsume
}
},
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
started = true
SimpleDragController(
onDrag = { /* do nothing */ },
@@ -668,7 +668,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { consumedOnDrag = it },
onStop = { consumedOnDragStop = it },
@@ -739,7 +739,7 @@ class MultiPointerDraggableTest {
.multiPointerDraggable(
orientation = Orientation.Vertical,
startDragImmediately = { false },
- onDragStarted = { _, _, _ ->
+ onDragStarted = { _, _ ->
SimpleDragController(
onDrag = { /* do nothing */ },
onStop = {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
index 2bc9b3826548..aaeaba93304d 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt
@@ -38,6 +38,7 @@ import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.PointerType
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.testTag
@@ -61,6 +62,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.compose.animation.scene.TestScenes.SceneA
import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
+import com.android.compose.animation.scene.TestScenes.SceneD
import com.android.compose.animation.scene.subjects.assertThat
import com.google.common.truth.Truth.assertThat
import org.junit.Rule
@@ -127,6 +129,7 @@ class SwipeToSceneTest {
mapOf(
Swipe.Down to SceneA,
Swipe(SwipeDirection.Down, pointerCount = 2) to SceneB,
+ Swipe(SwipeDirection.Down, pointersType = PointerType.Mouse) to SceneD,
Swipe(SwipeDirection.Right, fromSource = Edge.Left) to SceneB,
Swipe(SwipeDirection.Down, fromSource = Edge.Top) to SceneB,
)
@@ -134,6 +137,12 @@ class SwipeToSceneTest {
) {
Box(Modifier.fillMaxSize())
}
+ scene(
+ key = SceneD,
+ userActions = if (swipesEnabled()) mapOf(Swipe.Up to SceneC) else emptyMap(),
+ ) {
+ Box(Modifier.fillMaxSize())
+ }
}
}
@@ -502,6 +511,45 @@ class SwipeToSceneTest {
}
@Test
+ fun mousePointerSwipe() {
+ // Start at scene C.
+ val layoutState = layoutState(SceneC)
+
+ // 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
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ TestContent(layoutState)
+ }
+
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+
+ rule.onRoot().performMouseInput {
+ enter(middle)
+ press()
+ moveBy(Offset(0f, touchSlop + 10.dp.toPx()), 1_000)
+ }
+
+ // We are transitioning to D because we are moving the mouse while the primary button is
+ // pressed.
+ val transition = assertThat(layoutState.transitionState).isSceneTransition()
+ assertThat(transition).hasFromScene(SceneC)
+ assertThat(transition).hasToScene(SceneD)
+
+ rule.onRoot().performMouseInput {
+ release()
+ exit(middle)
+ }
+ // Release the mouse primary button and wait for the animation to end. We are back to C
+ // because we only swiped 10dp.
+ rule.waitForIdle()
+ assertThat(layoutState.transitionState).isIdle()
+ assertThat(layoutState.transitionState).hasCurrentScene(SceneC)
+ }
+
+ @Test
fun mouseWheel_pointerInputApi_ignoredByStl() {
val layoutState = layoutState()
var touchSlop = 0f
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index d66d6b3ab219..d31711496ff0 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -28,8 +28,8 @@ import com.android.compose.animation.scene.TestScenes.SceneB
import com.android.compose.animation.scene.TestScenes.SceneC
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.animation.scene.transformation.OverscrollTranslate
-import com.android.compose.animation.scene.transformation.Transformation
import com.android.compose.animation.scene.transformation.TransformationRange
+import com.android.compose.animation.scene.transformation.TransformationWithRange
import com.android.compose.test.transition
import com.google.common.truth.Correspondence
import com.google.common.truth.Truth.assertThat
@@ -310,7 +310,8 @@ class TransitionDslTest {
}
val overscrollSpec = transitions.overscrollSpecs.single()
- val transformation = overscrollSpec.transformationSpec.transformations.single()
+ val transformation =
+ overscrollSpec.transformationSpec.transformations.single().transformation
assertThat(transformation).isInstanceOf(OverscrollTranslate::class.java)
}
@@ -344,7 +345,7 @@ class TransitionDslTest {
companion object {
private val TRANSFORMATION_RANGE =
- Correspondence.transforming<Transformation, TransformationRange?>(
+ Correspondence.transforming<TransformationWithRange<*>, TransformationRange?>(
{ it?.range },
"has range equal to",
)
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
index f39dd676fb6e..95ef2ce821e1 100644
--- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestValues.kt
@@ -65,4 +65,6 @@ val EmptyTestTransitions = transitions {
}
from(TestScenes.SceneC, to = TestScenes.SceneA) { spec = snap() }
+
+ from(TestScenes.SceneC, to = TestScenes.SceneD) { spec = snap() }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index 0b55a6e3ffa1..d86c0d664590 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -39,7 +39,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
val digitLeftTopMap = mutableMapOf<Int, Point>()
var maxSingleDigitSize = Point(-1, -1)
val lockscreenTranslate = Point(0, 0)
- val aodTranslate = Point(0, 0)
+ var aodTranslate = Point(0, 0)
init {
setWillNotDraw(false)
@@ -64,8 +64,7 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
maxSingleDigitSize.y = max(maxSingleDigitSize.y, textView.measuredHeight)
}
val textView = digitalClockTextViewMap[R.id.HOUR_FIRST_DIGIT]!!
- aodTranslate.x = -(maxSingleDigitSize.x * AOD_HORIZONTAL_TRANSLATE_RATIO).toInt()
- aodTranslate.y = (maxSingleDigitSize.y * AOD_VERTICAL_TRANSLATE_RATIO).toInt()
+ aodTranslate = Point(0, 0)
return Point(
((maxSingleDigitSize.x + abs(aodTranslate.x)) * 2),
((maxSingleDigitSize.y + abs(aodTranslate.y)) * 2),
@@ -162,9 +161,6 @@ class FlexClockView(context: Context, val assets: AssetLoader, messageBuffer: Me
val AOD_TRANSITION_DURATION = 750L
val CHARGING_TRANSITION_DURATION = 300L
- val AOD_HORIZONTAL_TRANSLATE_RATIO = 0.15F
- val AOD_VERTICAL_TRANSLATE_RATIO = 0.075F
-
// Use the sign of targetTranslation to control the direction of digit translation
fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
val outPoint = Point(targetTranslation)
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 c0899e3006a6..5c84f2d04ccc 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
@@ -148,7 +148,11 @@ open class SimpleDigitalClockTextView(
lsFontVariation = ClockFontAxisSetting.toFVar(axes + OPTICAL_SIZE_AXIS)
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
- textAnimator.setTextStyle(fvar = lsFontVariation, animate = true)
+
+ lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
+ targetTextBounds.set(textBounds)
+
+ textAnimator.setTextStyle(fvar = lsFontVariation, animate = false)
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
recomputeMaxSingleDigitSizes()
requestLayout()
@@ -201,7 +205,7 @@ open class SimpleDigitalClockTextView(
} else {
textBounds.height() + 2 * lockScreenPaint.strokeWidth.toInt()
},
- MeasureSpec.getMode(measuredHeight),
+ MeasureSpec.getMode(measuredHeightAndState),
)
}
@@ -215,10 +219,10 @@ open class SimpleDigitalClockTextView(
} else {
max(
textBounds.width() + 2 * lockScreenPaint.strokeWidth.toInt(),
- MeasureSpec.getSize(measuredWidth),
+ MeasureSpec.getSize(measuredWidthAndState),
)
},
- MeasureSpec.getMode(measuredWidth),
+ MeasureSpec.getMode(measuredWidthAndState),
)
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
index 2a87452b0b6a..ae18aac66215 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepository.kt
@@ -16,102 +16,19 @@
package com.android.systemui.shared.settings.data.repository
-import android.content.ContentResolver
-import android.database.ContentObserver
import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
-/**
- * Defines interface for classes that can provide access to data from [Settings.Secure].
- * This repository doesn't guarantee to provide value across different users. For that
- * see: [UserAwareSecureSettingsRepository]
- */
+/** Defines interface for classes that can provide access to data from [Settings.Secure]. */
interface SecureSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
- fun intSetting(
- name: String,
- defaultValue: Int = 0,
- ): Flow<Int>
+ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun setInt(
- name: String,
- value: Int,
- )
+ suspend fun setInt(name: String, value: Int)
- suspend fun getInt(
- name: String,
- defaultValue: Int = 0,
- ): Int
+ suspend fun getInt(name: String, defaultValue: Int = 0): Int
suspend fun getString(name: String): String?
}
-
-class SecureSettingsRepositoryImpl(
- private val contentResolver: ContentResolver,
- private val backgroundDispatcher: CoroutineDispatcher,
-) : SecureSettingsRepository {
-
- override fun intSetting(
- name: String,
- defaultValue: Int,
- ): Flow<Int> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- contentResolver.registerContentObserver(
- Settings.Secure.getUriFor(name),
- /* notifyForDescendants= */ false,
- observer,
- )
- send(Unit)
-
- awaitClose { contentResolver.unregisterContentObserver(observer) }
- }
- .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
- // The above work is done on the background thread (which is important for accessing
- // settings through the content resolver).
- .flowOn(backgroundDispatcher)
- }
-
- override suspend fun setInt(name: String, value: Int) {
- withContext(backgroundDispatcher) {
- Settings.Secure.putInt(
- contentResolver,
- name,
- value,
- )
- }
- }
-
- override suspend fun getInt(name: String, defaultValue: Int): Int {
- return withContext(backgroundDispatcher) {
- Settings.Secure.getInt(
- contentResolver,
- name,
- defaultValue,
- )
- }
- }
-
- override suspend fun getString(name: String): String? {
- return withContext(backgroundDispatcher) {
- Settings.Secure.getString(
- contentResolver,
- name,
- )
- }
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
new file mode 100644
index 000000000000..8b9fcb496f59
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SecureSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Simple implementation of [SecureSettingsRepository].
+ *
+ * This repository doesn't guarantee to provide value across different users, and therefore
+ * shouldn't be used in SystemUI. For that see: [UserAwareSecureSettingsRepository]
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SecureSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SecureSettingsRepository {
+
+ override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.Secure.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ withContext(backgroundDispatcher) { Settings.Secure.putInt(contentResolver, name, value) }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getInt(contentResolver, name, defaultValue)
+ }
+ }
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.Secure.getString(contentResolver, name)
+ }
+ }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
index afe82fb2a5fa..8cda9b3d9985 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepository.kt
@@ -16,102 +16,19 @@
package com.android.systemui.shared.settings.data.repository
-import android.content.ContentResolver
-import android.database.ContentObserver
import android.provider.Settings
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.withContext
-/**
- * Defines interface for classes that can provide access to data from [Settings.System]. This
- * repository doesn't guarantee to provide value across different users. For that see:
- * [UserAwareSecureSettingsRepository] which does that for secure settings.
- */
+/** Interface for classes that can provide access to data from [Settings.System]. */
interface SystemSettingsRepository {
/** Returns a [Flow] tracking the value of a setting as an [Int]. */
- fun intSetting(
- name: String,
- defaultValue: Int = 0,
- ): Flow<Int>
+ fun intSetting(name: String, defaultValue: Int = 0): Flow<Int>
/** Updates the value of the setting with the given name. */
- suspend fun setInt(
- name: String,
- value: Int,
- )
+ suspend fun setInt(name: String, value: Int)
- suspend fun getInt(
- name: String,
- defaultValue: Int = 0,
- ): Int
+ suspend fun getInt(name: String, defaultValue: Int = 0): Int
suspend fun getString(name: String): String?
}
-
-class SystemSettingsRepositoryImpl(
- private val contentResolver: ContentResolver,
- private val backgroundDispatcher: CoroutineDispatcher,
-) : SystemSettingsRepository {
-
- override fun intSetting(
- name: String,
- defaultValue: Int,
- ): Flow<Int> {
- return callbackFlow {
- val observer =
- object : ContentObserver(null) {
- override fun onChange(selfChange: Boolean) {
- trySend(Unit)
- }
- }
-
- contentResolver.registerContentObserver(
- Settings.System.getUriFor(name),
- /* notifyForDescendants= */ false,
- observer,
- )
- send(Unit)
-
- awaitClose { contentResolver.unregisterContentObserver(observer) }
- }
- .map { Settings.System.getInt(contentResolver, name, defaultValue) }
- // The above work is done on the background thread (which is important for accessing
- // settings through the content resolver).
- .flowOn(backgroundDispatcher)
- }
-
- override suspend fun setInt(name: String, value: Int) {
- withContext(backgroundDispatcher) {
- Settings.System.putInt(
- contentResolver,
- name,
- value,
- )
- }
- }
-
- override suspend fun getInt(name: String, defaultValue: Int): Int {
- return withContext(backgroundDispatcher) {
- Settings.System.getInt(
- contentResolver,
- name,
- defaultValue,
- )
- }
- }
-
- override suspend fun getString(name: String): String? {
- return withContext(backgroundDispatcher) {
- Settings.System.getString(
- contentResolver,
- name,
- )
- }
- }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
new file mode 100644
index 000000000000..b039a320b987
--- /dev/null
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/settings/data/repository/SystemSettingsRepositoryImpl.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.shared.settings.data.repository
+
+import android.content.ContentResolver
+import android.database.ContentObserver
+import android.provider.Settings
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.withContext
+
+/**
+ * Defines interface for classes that can provide access to data from [Settings.System].
+ *
+ * This repository doesn't guarantee to provide value across different users. For that see:
+ * [UserAwareSystemSettingsRepository].
+ */
+// TODO: b/377244768 - Move to Theme/WallpaperPicker, away from SystemUI.
+class SystemSettingsRepositoryImpl(
+ private val contentResolver: ContentResolver,
+ private val backgroundDispatcher: CoroutineDispatcher,
+) : SystemSettingsRepository {
+
+ override fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return callbackFlow {
+ val observer =
+ object : ContentObserver(null) {
+ override fun onChange(selfChange: Boolean) {
+ trySend(Unit)
+ }
+ }
+
+ contentResolver.registerContentObserver(
+ Settings.System.getUriFor(name),
+ /* notifyForDescendants= */ false,
+ observer,
+ )
+ send(Unit)
+
+ awaitClose { contentResolver.unregisterContentObserver(observer) }
+ }
+ .map { Settings.System.getInt(contentResolver, name, defaultValue) }
+ // The above work is done on the background thread (which is important for accessing
+ // settings through the content resolver).
+ .flowOn(backgroundDispatcher)
+ }
+
+ override suspend fun setInt(name: String, value: Int) {
+ withContext(backgroundDispatcher) { Settings.System.putInt(contentResolver, name, value) }
+ }
+
+ override suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getInt(contentResolver, name, defaultValue)
+ }
+ }
+
+ override suspend fun getString(name: String): String? {
+ return withContext(backgroundDispatcher) {
+ Settings.System.getString(contentResolver, name)
+ }
+ }
+}
diff --git a/packages/SystemUI/docs/demo_mode.md b/packages/SystemUI/docs/demo_mode.md
index ade5171d6415..c18d1f143b85 100644
--- a/packages/SystemUI/docs/demo_mode.md
+++ b/packages/SystemUI/docs/demo_mode.md
@@ -35,6 +35,7 @@ Commands are sent as string extras with key ```command``` (required). Possible v
| | ```fully``` | | Sets MCS state to fully connected (```true```, ```false```)
| | ```wifi``` | | ```show``` to show icon, any other value to hide
| | | ```level``` | Sets wifi level (null or 0-4)
+| | | ```hotspot``` | Sets the wifi to be from an Instant Hotspot. Values: ```none```, ```unknown```, ```phone```, ```tablet```, ```laptop```, ```watch```, ```auto```. (See `DemoModeWifiDataSource.kt`.)
| | ```mobile``` | | ```show``` to show icon, any other value to hide
| | | ```datatype``` | Values: ```1x```, ```3g```, ```4g```, ```e```, ```g```, ```h```, ```lte```, ```roam```, any other value to hide
| | | ```level``` | Sets mobile signal strength level (null or 0-4)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
new file mode 100644
index 000000000000..8635bb0e8ab2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/OnTeardownRuleTest.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.JUnitCore
+
+@Suppress("JUnitMalformedDeclaration")
+@SmallTest
+class OnTeardownRuleTest : SysuiTestCase() {
+ // None of these inner classes should be run except as part of this utilities-testing test
+ class HasTeardown {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun teardownRuns() {
+ val result = JUnitCore().run(HasTeardown::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(HasTeardown.teardownWasRun).isTrue()
+ }
+
+ class FirstTeardownFails {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ teardownWasRun = false
+ teardownRule.onTeardown { fail("One fails") }
+ teardownRule.onTeardown { teardownWasRun = true }
+ }
+
+ @Test fun doTest() {}
+
+ companion object {
+ var teardownWasRun = false
+ }
+ }
+
+ @Test
+ fun allTeardownsRun() {
+ val result = JUnitCore().run(FirstTeardownFails::class.java)
+ assertThat(result.failures.map { it.message }).isEqualTo(listOf("One fails"))
+ assertThat(FirstTeardownFails.teardownWasRun).isTrue()
+ }
+
+ class ThreeTeardowns {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Before
+ fun setUp() {
+ messages.clear()
+ }
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown { messages.add("A") }
+ teardownRule.onTeardown { messages.add("B") }
+ teardownRule.onTeardown { messages.add("C") }
+ }
+
+ companion object {
+ val messages = mutableListOf<String>()
+ }
+ }
+
+ @Test
+ fun reverseOrder() {
+ val result = JUnitCore().run(ThreeTeardowns::class.java)
+ assertThat(result.failures).isEmpty()
+ assertThat(ThreeTeardowns.messages).isEqualTo(listOf("C", "B", "A"))
+ }
+
+ class TryToDoABadThing {
+ @get:Rule val teardownRule = OnTeardownRule()
+
+ @Test
+ fun doTest() {
+ teardownRule.onTeardown {
+ teardownRule.onTeardown {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ @Test
+ fun prohibitTeardownDuringTeardown() {
+ val result = JUnitCore().run(TryToDoABadThing::class.java)
+ assertThat(result.failures.map { it.message })
+ .isEqualTo(listOf("Cannot add new teardown routines after test complete."))
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
new file mode 100644
index 000000000000..a2001e66e55b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/OWNERS
@@ -0,0 +1 @@
+include /core/java/android/view/accessibility/OWNERS \ No newline at end of file
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
index 176c3ac43936..2594472a9c8a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/AccessibilityQsShortcutsRepositoryImplTest.kt
@@ -22,12 +22,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.collectLastValue
+import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -40,8 +41,6 @@ import org.mockito.junit.MockitoRule
@RunWith(AndroidJUnit4::class)
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val testDispatcher = kosmos.testDispatcher
- private val testScope = kosmos.testScope
private val secureSettings = kosmos.fakeSettings
@Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()
@@ -55,8 +54,8 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
return UserA11yQsShortcutsRepository(
userId,
secureSettings,
- testScope.backgroundScope,
- testDispatcher,
+ kosmos.testScope.backgroundScope,
+ kosmos.testDispatcher,
)
}
}
@@ -69,13 +68,13 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
AccessibilityQsShortcutsRepositoryImpl(
a11yManager,
userA11yQsShortcutsRepositoryFactory,
- testDispatcher
+ kosmos.testDispatcher,
)
}
@Test
fun a11yQsShortcutTargetsForCorrectUsers() =
- testScope.runTest {
+ kosmos.runTest {
val user0 = 0
val targetsForUser0 = setOf("a", "b", "c")
val user1 = 1
@@ -94,7 +93,7 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
secureSettings.putStringForUser(
SETTING_NAME,
a11yQsTargets.joinToString(separator = ":"),
- forUser
+ forUser,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 8c7cd619a158..cdda9ccc9b9e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -47,8 +47,7 @@ import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.domain.interactor.ShadeBackActionInteractor
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -61,7 +60,6 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
import org.junit.Rule
@@ -95,9 +93,6 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
- private val activeNotificationsRepository = ActiveNotificationListRepository()
- private val activeNotificationsInteractor =
- ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -107,7 +102,7 @@ class BackActionInteractorTest : SysuiTestCase() {
keyguardRepository,
headsUpManager,
powerInteractor,
- activeNotificationsInteractor,
+ kosmos.activeNotificationsInteractor,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
index 44ce08514dee..c3c958ca0e94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerServiceTest.kt
@@ -20,6 +20,8 @@ import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Intent
+import android.content.IntentSender
+import android.os.Binder
import android.os.UserHandle
import android.testing.TestableLooper
import android.widget.RemoteViews
@@ -29,6 +31,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
@@ -43,11 +46,13 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -164,7 +169,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
}
@Test
- fun addWidget_getWidgetUpdate() =
+ fun addWidget_noConfigurationCallback_getWidgetUpdate() =
testScope.runTest {
setupWidgets()
@@ -180,7 +185,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
// Add a widget
- service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3)
+ service.addWidget(ComponentName("pkg_4", "cls_4"), UserHandle.of(0), 3, null)
runCurrent()
// Verify an update pushed with widget 4 added
@@ -192,6 +197,71 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
}
@Test
+ fun addWidget_withConfigurationCallback_configurationFails_doNotAddWidget() =
+ testScope.runTest {
+ setupWidgets()
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ // Verify the update is as expected
+ val widgets by collectLastValue(service.listenForWidgetUpdates())
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+ // Add a widget with a configuration callback that fails
+ service.addWidget(
+ ComponentName("pkg_4", "cls_4"),
+ UserHandle.of(0),
+ 3,
+ createConfigureWidgetCallback(success = false),
+ )
+ runCurrent()
+
+ // Verify that widget 4 is not added
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+ }
+
+ @Test
+ fun addWidget_withConfigurationCallback_configurationSucceeds_addWidget() =
+ testScope.runTest {
+ setupWidgets()
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ // Verify the update is as expected
+ val widgets by collectLastValue(service.listenForWidgetUpdates())
+ assertThat(widgets).hasSize(3)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+
+ // Add a widget with a configuration callback that fails
+ service.addWidget(
+ ComponentName("pkg_4", "cls_4"),
+ UserHandle.of(0),
+ 3,
+ createConfigureWidgetCallback(success = true),
+ )
+ runCurrent()
+
+ // Verify that widget 4 is added
+ assertThat(widgets).hasSize(4)
+ assertThat(widgets?.get(0)?.has(1, "pkg_1/cls_1", 0, 3)).isTrue()
+ assertThat(widgets?.get(1)?.has(2, "pkg_2/cls_2", 1, 3)).isTrue()
+ assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
+ assertThat(widgets?.get(3)?.has(4, "pkg_4/cls_4", 3, 3)).isTrue()
+ }
+
+ @Test
fun deleteWidget_getWidgetUpdate() =
testScope.runTest {
setupWidgets()
@@ -271,6 +341,21 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
assertThat(widgets?.get(2)?.has(3, "pkg_3/cls_3", 2, 6)).isTrue()
}
+ @Test
+ fun getIntentSenderForConfigureActivity() =
+ testScope.runTest {
+ val expected = IntentSender(Binder())
+ whenever(appWidgetHost.getIntentSenderForConfigureActivity(anyInt(), anyInt()))
+ .thenReturn(expected)
+
+ // Bind service
+ val binder = underTest.onBind(Intent())
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+
+ val actual = service.getIntentSenderForConfigureActivity(1)
+ assertThat(actual).isEqualTo(expected)
+ }
+
private fun setupWidgets() {
widgetRepository.addWidget(
appWidgetId = 1,
@@ -293,7 +378,7 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
}
private fun IGlanceableHubWidgetManagerService.listenForWidgetUpdates() =
- conflatedCallbackFlow<List<CommunalWidgetContentModel>> {
+ conflatedCallbackFlow {
val listener =
object : IGlanceableHubWidgetsListener.Stub() {
override fun onWidgetsUpdated(widgets: List<CommunalWidgetContentModel>) {
@@ -316,4 +401,15 @@ class GlanceableHubWidgetManagerServiceTest : SysuiTestCase() {
this.rank == rank &&
this.spanY == spanY
}
+
+ private fun createConfigureWidgetCallback(success: Boolean): IConfigureWidgetCallback {
+ return object : IConfigureWidgetCallback.Stub() {
+ override fun onConfigureWidget(
+ appWidgetId: Int,
+ resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+ ) {
+ resultReceiver?.onResult(success)
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
index 5d4eaf07be25..e1bdf1c42c9a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetConfigurationControllerTest.kt
@@ -18,17 +18,18 @@ package com.android.systemui.communal.widgets
import android.app.Activity
import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.Binder
+import android.os.OutcomeReceiver
import androidx.activity.ComponentActivity
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.shared.model.fakeGlanceableHubMultiUserHelper
+import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.async
@@ -38,15 +39,22 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class WidgetConfigurationControllerTest : SysuiTestCase() {
- @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
- @Mock private lateinit var ownerActivity: ComponentActivity
+ private val appWidgetHost = mock<CommunalAppWidgetHost>()
+ private val ownerActivity = mock<ComponentActivity>()
+
+ private val outcomeReceiverCaptor = argumentCaptor<OutcomeReceiver<IntentSender?, Throwable>>()
private val kosmos = testKosmos()
@@ -54,18 +62,19 @@ class WidgetConfigurationControllerTest : SysuiTestCase() {
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
underTest =
WidgetConfigurationController(
ownerActivity,
{ appWidgetHost },
kosmos.testDispatcher,
kosmos.fakeGlanceableHubMultiUserHelper,
+ { kosmos.mockGlanceableHubWidgetManager },
+ kosmos.fakeExecutor,
)
}
@Test
- fun configurationFailsWhenActivityNotFound() =
+ fun configureWidget_activityNotFound_returnsFalse() =
with(kosmos) {
testScope.runTest {
whenever(
@@ -84,13 +93,97 @@ class WidgetConfigurationControllerTest : SysuiTestCase() {
}
@Test
- fun configurationFails() =
+ fun configureWidget_configurationFails_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+ assertThat(result.isCompleted).isFalse()
+
+ underTest.setConfigurationResult(Activity.RESULT_CANCELED)
+ runCurrent()
+
+ assertThat(result.await()).isFalse()
+ result.cancel()
+ }
+ }
+
+ @Test
+ fun configureWidget_configurationSucceeds_returnsTrue() =
+ with(kosmos) {
+ testScope.runTest {
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+ assertThat(result.isCompleted).isFalse()
+
+ underTest.setConfigurationResult(Activity.RESULT_OK)
+ runCurrent()
+
+ assertThat(result.await()).isTrue()
+ result.cancel()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_activityNotFound_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ // Activity not found
+ whenever(
+ mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+ anyInt(),
+ outcomeReceiverCaptor.capture(),
+ any(),
+ )
+ )
+ .then { outcomeReceiverCaptor.firstValue.onError(ActivityNotFoundException()) }
+
+ val result = async { underTest.configureWidget(123) }
+ runCurrent()
+
+ assertThat(result.await()).isFalse()
+ result.cancel()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_intentSenderNull_returnsFalse() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ prepareIntentSender(null)
+
+ assertThat(underTest.configureWidget(123)).isFalse()
+ }
+ }
+
+ @Test
+ fun configureWidget_headlessSystemUser_configurationFails_returnsFalse() =
with(kosmos) {
testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ val intentSender = IntentSender(Binder())
+ prepareIntentSender(intentSender)
+
val result = async { underTest.configureWidget(123) }
runCurrent()
assertThat(result.isCompleted).isFalse()
+ verify(ownerActivity)
+ .startIntentSenderForResult(
+ eq(intentSender),
+ eq(WidgetConfigurationController.REQUEST_CODE),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any(),
+ )
+
underTest.setConfigurationResult(Activity.RESULT_CANCELED)
runCurrent()
@@ -100,13 +193,29 @@ class WidgetConfigurationControllerTest : SysuiTestCase() {
}
@Test
- fun configurationSuccessful() =
+ fun configureWidget_headlessSystemUser_configurationSucceeds_returnsTrue() =
with(kosmos) {
testScope.runTest {
+ fakeGlanceableHubMultiUserHelper.setIsInHeadlessSystemUser(true)
+
+ val intentSender = IntentSender(Binder())
+ prepareIntentSender(intentSender)
+
val result = async { underTest.configureWidget(123) }
runCurrent()
assertThat(result.isCompleted).isFalse()
+ verify(ownerActivity)
+ .startIntentSenderForResult(
+ eq(intentSender),
+ eq(WidgetConfigurationController.REQUEST_CODE),
+ anyOrNull(),
+ anyInt(),
+ anyInt(),
+ anyInt(),
+ any(),
+ )
+
underTest.setConfigurationResult(Activity.RESULT_OK)
runCurrent()
@@ -114,4 +223,16 @@ class WidgetConfigurationControllerTest : SysuiTestCase() {
result.cancel()
}
}
+
+ private fun prepareIntentSender(intentSender: IntentSender?) =
+ with(kosmos) {
+ whenever(
+ mockGlanceableHubWidgetManager.getIntentSenderForConfigureActivity(
+ anyInt(),
+ outcomeReceiverCaptor.capture(),
+ any(),
+ )
+ )
+ .then { outcomeReceiverCaptor.firstValue.onResult(intentSender) }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
index f8a45e82c2ab..b343def58e29 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteServiceBinderTest.kt
@@ -31,7 +31,8 @@ import com.android.systemui.controls.panels.authorizedPanelsRepository
import com.android.systemui.controls.panels.selectedComponentRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsComponent
import com.android.systemui.dreams.homecontrols.system.domain.interactor.controlsListingController
import com.android.systemui.dreams.homecontrols.system.domain.interactor.homeControlsComponentInteractor
@@ -42,13 +43,10 @@ import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.mockito.withArgCaptor
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -56,6 +54,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito
+import org.mockito.kotlin.whenever
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -90,13 +89,13 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
fun testRegisterSingleListener() =
testScope.runTest {
setup()
- val controlsSettings by collectLastValue(addCallback())
+ val controlsSettings by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -106,21 +105,21 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
fun testRegisterMultipleListeners() =
testScope.runTest {
setup()
- val controlsSettings1 by collectLastValue(addCallback())
- val controlsSettings2 by collectLastValue(addCallback())
+ val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+ val controlsSettings2 by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings1)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
assertThat(controlsSettings2)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -130,13 +129,13 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
fun testListenerCalledWhenStateChanges() =
testScope.runTest {
setup()
- val controlsSettings by collectLastValue(addCallback())
+ val controlsSettings by collectLastValue(underTest.controlsSettings)
runServicesUpdate()
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(
- panelComponent = TEST_COMPONENT,
+ HomeControlsComponentInfo(
+ componentName = TEST_COMPONENT,
allowTrivialControlsOnLockscreen = false,
)
)
@@ -146,13 +145,47 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
// Updated with null component now that we are no longer authorized.
assertThat(controlsSettings)
.isEqualTo(
- CallbackArgs(panelComponent = null, allowTrivialControlsOnLockscreen = false)
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
)
}
+ @Test
+ fun testDestroy() =
+ testScope.runTest {
+ setup()
+ val controlsSettings1 by collectLastValue(underTest.controlsSettings)
+
+ assertThat(controlsSettings1)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+
+ underTest.onDestroy()
+ runServicesUpdate()
+ fakeControlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+ // Existing callback is not triggered if destroyed.
+ assertThat(controlsSettings1)
+ .isEqualTo(
+ HomeControlsComponentInfo(
+ componentName = null,
+ allowTrivialControlsOnLockscreen = false,
+ )
+ )
+ // New callbacks cannot be added.
+ val controlsSettings2 by collectLastValue(underTest.controlsSettings)
+ assertThat(controlsSettings2).isNull()
+ }
+
private fun TestScope.runServicesUpdate() {
runCurrent()
- val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
+ val listings = listOf(buildControlsServiceInfo(TEST_COMPONENT, "panel", hasPanel = true))
val callback = withArgCaptor {
Mockito.verify(kosmos.controlsListingController).addCallback(capture())
}
@@ -160,20 +193,6 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
runCurrent()
}
- private fun addCallback() = conflatedCallbackFlow {
- val callback =
- object : IOnControlsSettingsChangeListener.Stub() {
- override fun onControlsSettingsChanged(
- panelComponent: ComponentName?,
- allowTrivialControlsOnLockscreen: Boolean,
- ) {
- trySend(CallbackArgs(panelComponent, allowTrivialControlsOnLockscreen))
- }
- }
- underTest.registerListenerForCurrentUser(callback)
- awaitClose { underTest.unregisterListenerForCurrentUser(callback) }
- }
-
private suspend fun TestScope.setup() {
kosmos.fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
kosmos.fakeUserTracker.set(listOf(PRIMARY_USER), 0)
@@ -182,12 +201,7 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
runCurrent()
}
- private data class CallbackArgs(
- val panelComponent: ComponentName?,
- val allowTrivialControlsOnLockscreen: Boolean,
- )
-
- private fun ControlsServiceInfo(
+ private fun buildControlsServiceInfo(
componentName: ComponentName,
label: CharSequence,
hasPanel: Boolean,
@@ -225,7 +239,7 @@ class HomeControlsRemoteServiceBinderTest : SysuiTestCase() {
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
- /* flags= */ UserInfo.FLAG_PRIMARY,
+ /* flags= */ UserInfo.FLAG_MAIN,
)
private const val TEST_PACKAGE = "pkg"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
index f331060db43e..5827c7b444d7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.table.TableLogBuffer
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
@@ -134,6 +135,21 @@ class DumpManagerTest : SysuiTestCase() {
}
@Test
+ fun registerDumpable_supportsAnonymousDumpables() {
+ val anonDumpable =
+ object : Dumpable {
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("AnonDumpable")
+ }
+ }
+
+ // THEN registration with implicit names should succeed
+ dumpManager.registerCriticalDumpable(anonDumpable)
+
+ // No exception thrown
+ }
+
+ @Test
fun getDumpables_returnsSafeCollection() {
// GIVEN a variety of registered dumpables
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
index 3388a785a26a..20cd8608d204 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt
@@ -28,11 +28,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.FakeUserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.Serializable
@@ -68,6 +70,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
@Mock private lateinit var systemProperties: SystemPropertiesHelper
@Mock private lateinit var resources: Resources
@Mock private lateinit var restarter: Restarter
+ private lateinit var fakeExecutor: FakeExecutor
private lateinit var userTracker: FakeUserTracker
private val flagMap = mutableMapOf<String, Flag<*>>()
private lateinit var broadcastReceiver: BroadcastReceiver
@@ -83,6 +86,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA)
flagMap.put(releasedFlagB.name, releasedFlagB)
+ fakeExecutor = FakeExecutor(FakeSystemClock())
userTracker = FakeUserTracker(onCreateCurrentUserContext = { mockContext })
mFeatureFlagsClassicDebug =
@@ -95,7 +99,8 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
serverFlagReader,
flagMap,
restarter,
- userTracker
+ userTracker,
+ fakeExecutor,
)
mFeatureFlagsClassicDebug.init()
verify(flagManager).onSettingsChangedAction = any()
@@ -325,14 +330,14 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
// trying to erase an id not in the map does nothing
broadcastReceiver.onReceive(
mockContext,
- Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "")
+ Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, ""),
)
verifyNoMoreInteractions(flagManager, globalSettings)
// valid id with no value puts empty string in the setting
broadcastReceiver.onReceive(
mockContext,
- Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1")
+ Intent(FlagManager.ACTION_SET_FLAG).putExtra(FlagManager.EXTRA_NAME, "1"),
)
verifyPutData("1", "", numReads = 0)
}
@@ -415,7 +420,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
serverFlagReader.setFlagValue(
teamfoodableFlagA.namespace,
teamfoodableFlagA.name,
- !teamfoodableFlagA.default
+ !teamfoodableFlagA.default,
)
verify(restarter, never()).restartSystemUI(anyString())
}
@@ -428,7 +433,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
serverFlagReader.setFlagValue(
teamfoodableFlagA.namespace,
teamfoodableFlagA.name,
- !teamfoodableFlagA.default
+ !teamfoodableFlagA.default,
)
verify(restarter).restartSystemUI(anyString())
}
@@ -441,7 +446,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() {
serverFlagReader.setFlagValue(
teamfoodableFlagA.namespace,
teamfoodableFlagA.name,
- teamfoodableFlagA.default
+ teamfoodableFlagA.default,
)
verify(restarter, never()).restartSystemUI(anyString())
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index 26ce67da10b3..7ec53dfbdd10 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -40,8 +40,8 @@ import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.systemui.util.settings.fakeSettings
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -72,14 +72,13 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
@Before
fun setup() {
- val settingsRepository =
- UserAwareSecureSettingsRepositoryImpl(secureSettings, userRepository, dispatcher)
+ val settingsRepository = kosmos.userAwareSecureSettingsRepository
val stickyKeysRepository =
StickyKeysRepositoryImpl(
inputManager,
dispatcher,
settingsRepository,
- mock<StickyKeysLogger>()
+ mock<StickyKeysLogger>(),
)
setStickyKeySetting(enabled = false)
viewModel =
@@ -114,7 +113,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
verify(inputManager)
.registerStickyModifierStateListener(
any(),
- any(InputManager.StickyModifierStateListener::class.java)
+ any(InputManager.StickyModifierStateListener::class.java),
)
}
}
@@ -187,11 +186,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
assertThat(stickyKeys)
.isEqualTo(
- mapOf(
- ALT to Locked(false),
- META to Locked(false),
- SHIFT to Locked(false),
- )
+ mapOf(ALT to Locked(false), META to Locked(false), SHIFT to Locked(false))
)
}
}
@@ -218,7 +213,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
mapOf(
META to false,
SHIFT to false, // shift is sticky but not locked
- CTRL to false
+ CTRL to false,
)
)
val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
@@ -228,7 +223,7 @@ class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
SHIFT to false,
SHIFT to true, // shift is now locked
META to false,
- CTRL to false
+ CTRL to false,
)
)
assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
index 6e16705b0739..2c12f8782ddc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -34,6 +34,8 @@ import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticati
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.Transition
import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -115,9 +117,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // We should start with the surface invisible on LOCKSCREEN.
+ false // We should start with the surface invisible on LOCKSCREEN.
),
- values
+ values,
)
val lockscreenSpecificSurfaceVisibility = true
@@ -134,13 +136,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// We started a transition from LOCKSCREEN, we should be using the value emitted by the
// lockscreenSurfaceVisibilityFlow.
- assertEquals(
- listOf(
- false,
- lockscreenSpecificSurfaceVisibility,
- ),
- values
- )
+ assertEquals(listOf(false, lockscreenSpecificSurfaceVisibility), values)
// Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
transitionRepository.sendTransitionStep(
@@ -166,7 +162,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
lockscreenSpecificSurfaceVisibility,
false, // FINISHED (LOCKSCREEN)
),
- values
+ values,
)
val bouncerSpecificVisibility = true
@@ -191,13 +187,13 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
false,
bouncerSpecificVisibility,
),
- values
+ values,
)
}
@Test
@EnableSceneContainer
- fun surfaceBehindVisibility_fromLockscreenToGone_noUserInput_trueThroughout() =
+ fun surfaceBehindVisibility_fromLockscreenToGone_dependsOnDeviceEntry() =
testScope.runTest {
val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
@@ -212,7 +208,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
SuccessFingerprintAuthenticationStatus(0, true)
)
- // Start the transition to Gone, the surface should become immediately visible.
+ // Start the transition to Gone, the surface should remain invisible.
kosmos.setSceneTransition(
ObservableTransitionState.Transition(
fromScene = Scenes.Lockscreen,
@@ -224,9 +220,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
)
)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- assertThat(isSurfaceBehindVisible).isTrue()
+ assertThat(isSurfaceBehindVisible).isFalse()
- // Towards the end of the transition, the surface should continue to be visible.
+ // Towards the end of the transition, the surface should continue to remain invisible.
kosmos.setSceneTransition(
ObservableTransitionState.Transition(
fromScene = Scenes.Lockscreen,
@@ -238,7 +234,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
)
)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- assertThat(isSurfaceBehindVisible).isTrue()
+ assertThat(isSurfaceBehindVisible).isFalse()
// After the transition, settles on Gone. Surface behind should stay visible now.
kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Gone))
@@ -249,43 +245,6 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
@Test
@EnableSceneContainer
- fun surfaceBehindVisibility_fromLockscreenToGone_withUserInput_falseUntilInputStops() =
- testScope.runTest {
- val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
- val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
-
- // Before the transition, we start on Lockscreen so the surface should start invisible.
- kosmos.setSceneTransition(ObservableTransitionState.Idle(Scenes.Lockscreen))
- assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- assertThat(isSurfaceBehindVisible).isFalse()
-
- // Unlocked with fingerprint.
- kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
-
- // Start the transition to Gone, the surface should not be visible while
- // isUserInputOngoing is true
- val isUserInputOngoing = MutableStateFlow(true)
- kosmos.setSceneTransition(
- ObservableTransitionState.Transition(
- fromScene = Scenes.Lockscreen,
- toScene = Scenes.Gone,
- isInitiatedByUserInput = true,
- isUserInputOngoing = isUserInputOngoing,
- progress = flowOf(0.51f),
- currentScene = flowOf(Scenes.Gone),
- )
- )
- assertThat(isSurfaceBehindVisible).isFalse()
-
- // When isUserInputOngoing becomes false, then the surface should become visible.
- isUserInputOngoing.value = false
- assertThat(isSurfaceBehindVisible).isTrue()
- }
-
- @Test
- @EnableSceneContainer
fun surfaceBehindVisibility_fromBouncerToGone_becomesTrue() =
testScope.runTest {
val isSurfaceBehindVisible by collectLastValue(underTest.value.surfaceBehindVisibility)
@@ -362,20 +321,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
- listOf(
- Scenes.Shade,
- Scenes.QuickSettings,
- Scenes.Shade,
- Scenes.Gone,
- )
- .forEach { scene ->
- kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
- kosmos.sceneInteractor.changeScene(scene, "")
- assertThat(currentScene).isEqualTo(scene)
- assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
- .that(isSurfaceBehindVisible)
- .isTrue()
- }
+ listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Gone).forEach { scene ->
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+ kosmos.sceneInteractor.changeScene(scene, "")
+ assertThat(currentScene).isEqualTo(scene)
+ assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+ .that(isSurfaceBehindVisible)
+ .isTrue()
+ }
}
@Test
@@ -386,19 +339,14 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- listOf(
- Scenes.Shade,
- Scenes.QuickSettings,
- Scenes.Shade,
- Scenes.Lockscreen,
- )
- .forEach { scene ->
- kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
- kosmos.sceneInteractor.changeScene(scene, "")
- assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
- .that(isSurfaceBehindVisible)
- .isFalse()
- }
+ listOf(Scenes.Shade, Scenes.QuickSettings, Scenes.Shade, Scenes.Lockscreen).forEach {
+ scene ->
+ kosmos.setSceneTransition(ObservableTransitionState.Idle(scene))
+ kosmos.sceneInteractor.changeScene(scene, "")
+ assertWithMessage("Unexpected visibility for scene \"${scene.debugName}\"")
+ .that(isSurfaceBehindVisible)
+ .isFalse()
+ }
}
@Test
@@ -427,9 +375,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ false // Not using the animation when we're just sitting on LOCKSCREEN.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(true)
@@ -437,7 +385,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -446,7 +394,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
false,
true, // Still true when we're FINISHED -> GONE, since we're still animating.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(false)
@@ -458,7 +406,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false, // False once the animation ends.
),
- values
+ values,
)
}
@@ -488,9 +436,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- false, // Not using the animation when we're just sitting on LOCKSCREEN.
+ false // Not using the animation when we're just sitting on LOCKSCREEN.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(true)
@@ -509,7 +457,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
false,
true, // We're happily animating while transitioning to gone.
),
- values
+ values,
)
// Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
@@ -536,7 +484,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false, // Despite the animator still running, this should be false.
),
- values
+ values,
)
surfaceBehindIsAnimatingFlow.emit(false)
@@ -548,7 +496,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false, // The animator ending should have no effect.
),
- values
+ values,
)
}
@@ -579,10 +527,10 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- true, // Unsurprisingly, we should start with the lockscreen visible on
+ true // Unsurprisingly, we should start with the lockscreen visible on
// LOCKSCREEN.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -596,9 +544,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
assertEquals(
listOf(
- true, // Lockscreen remains visible while we're transitioning to GONE.
+ true // Lockscreen remains visible while we're transitioning to GONE.
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -615,7 +563,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false, // Once we're fully GONE, the lockscreen should not be visible.
),
- values
+ values,
)
}
@@ -628,7 +576,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -640,7 +588,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Then, false, since we finish in GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -665,7 +613,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Should remain false as we transition from GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -693,7 +641,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// visibility of the from state (LS).
true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -706,14 +654,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
runCurrent()
- assertEquals(
- listOf(
- true,
- false,
- true,
- ),
- values
- )
+ assertEquals(listOf(true, false, true), values)
}
/**
@@ -730,7 +671,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -740,7 +681,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Not visible since we're GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -803,7 +744,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// STARTED to GONE after a CANCELED from GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionSteps(
@@ -820,7 +761,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// visible again once we're finished in LOCKSCREEN.
true,
),
- values
+ values,
)
}
@@ -833,7 +774,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.GONE,
- testScope
+ testScope,
)
runCurrent()
@@ -843,7 +784,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Not visible when finished in GONE.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -869,7 +810,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
// Still not visible during GONE -> AOD.
false,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -886,9 +827,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false,
// Visible now that we're FINISHED in AOD.
- true
+ true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -914,9 +855,9 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
true,
false,
// Remains visible from AOD during transition.
- true
+ true,
),
- values
+ values,
)
transitionRepository.sendTransitionStep(
@@ -934,15 +875,15 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
false,
true,
// Until we're finished in GONE again.
- false
+ false,
),
- values
+ values,
)
}
@Test
@EnableSceneContainer
- fun lockscreenVisibility() =
+ fun lockscreenVisibilityWithScenes() =
testScope.runTest {
val isDeviceUnlocked by
collectLastValue(
@@ -956,32 +897,69 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
val lockscreenVisibility by collectLastValue(underTest.value.lockscreenVisibility)
assertThat(lockscreenVisibility).isTrue()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
+ kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Transition(from = Scenes.QuickSettings, to = Scenes.Shade))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+ kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
+ assertThat(currentScene).isEqualTo(Scenes.Shade)
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Bouncer))
kosmos.sceneInteractor.changeScene(Scenes.Bouncer, "")
assertThat(currentScene).isEqualTo(Scenes.Bouncer)
assertThat(lockscreenVisibility).isTrue()
+ kosmos.setSceneTransition(Transition(from = Scenes.Bouncer, to = Scenes.Gone))
+ assertThat(lockscreenVisibility).isTrue()
+
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
kosmos.authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
assertThat(isDeviceUnlocked).isTrue()
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Transition(from = Scenes.Shade, to = Scenes.QuickSettings))
+ assertThat(lockscreenVisibility).isFalse()
+
+ kosmos.setSceneTransition(Idle(Scenes.QuickSettings))
kosmos.sceneInteractor.changeScene(Scenes.QuickSettings, "")
assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
kosmos.sceneInteractor.changeScene(Scenes.Shade, "")
assertThat(currentScene).isEqualTo(Scenes.Shade)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
kosmos.sceneInteractor.changeScene(Scenes.Gone, "")
assertThat(currentScene).isEqualTo(Scenes.Gone)
assertThat(lockscreenVisibility).isFalse()
+ kosmos.setSceneTransition(Transition(from = Scenes.Gone, to = Scenes.Lockscreen))
+ assertThat(lockscreenVisibility).isFalse()
+
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "")
assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
assertThat(lockscreenVisibility).isTrue()
@@ -1037,7 +1015,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
private val goneToLs =
@@ -1047,7 +1025,7 @@ class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
flowOf(Scenes.Lockscreen),
progress,
false,
- flowOf(false)
+ flowOf(false),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
index 9c58e2b987a1..92764ae94271 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -29,11 +29,13 @@ import com.android.wm.shell.keyguard.KeyguardTransitions
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -112,4 +114,20 @@ class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
verify(activityTaskManagerService).setLockScreenShown(true, false)
verifyNoMoreInteractions(activityTaskManagerService)
}
+
+ @Test
+ fun setSurfaceBehindVisibility_goesAwayFirst_andIgnoresSecondCall() {
+ underTest.setLockscreenShown(true)
+ underTest.setSurfaceBehindVisibility(true)
+ verify(activityTaskManagerService).keyguardGoingAway(0)
+
+ underTest.setSurfaceBehindVisibility(true)
+ verifyNoMoreInteractions(keyguardTransitions)
+ }
+
+ @Test
+ fun setSurfaceBehindVisibility_falseSetsLockscreenVisibility() {
+ underTest.setSurfaceBehindVisibility(false)
+ verify(activityTaskManagerService).setLockScreenShown(eq(true), any())
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
index 7f0937059494..0e3b03f74c02 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -44,6 +44,7 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Ignore
@@ -107,6 +108,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
fun translationAndScale_whenNotDozing() =
testScope.runTest {
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to not dozing (on lockscreen)
keyguardTransitionRepository.sendTransitionStep(
@@ -180,6 +182,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
testScope.runTest {
underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100))
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -221,6 +224,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
testScope.runTest {
underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -263,6 +267,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
testScope.runTest {
underTest.updateBurnInParams(burnInParameters.copy(minViewY = 100, topInset = 80))
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -305,6 +310,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -423,6 +429,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
.thenReturn(if (isWeatherClock) true else false)
val movement by collectLastValue(underTest.movement)
+ assertThat(movement?.translationX).isEqualTo(0)
// Set to dozing (on AOD)
keyguardTransitionRepository.sendTransitionStep(
@@ -434,6 +441,7 @@ class AodBurnInViewModelTest : SysuiTestCase() {
),
validateStep = false,
)
+ runCurrent()
// Trigger a change to the burn-in model
burnInFlow.value = BurnInModel(translationX = 20, translationY = 30, scale = 0.5f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
new file mode 100644
index 000000000000..9e3fdf377b83
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryTest.kt
@@ -0,0 +1,335 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.media.session.MediaSession
+import android.os.Bundle
+import android.os.Handler
+import android.os.looper
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.graphics.imageLoader
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.media.controls.shared.mediaLogger
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.fakeMediaControllerFactory
+import com.android.systemui.media.controls.util.fakeSessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.testKosmos
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.clearInvocations
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+private const val PACKAGE_NAME = "package_name"
+private const val CUSTOM_ACTION_NAME = "Custom Action"
+private const val CUSTOM_ACTION_COMMAND = "custom-action"
+
+@SmallTest
+@RunWithLooper
+@RunWith(AndroidJUnit4::class)
+class Media3ActionFactoryTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val controllerFactory = kosmos.fakeMediaControllerFactory
+ private val tokenFactory = kosmos.fakeSessionTokenFactory
+ private lateinit var testableLooper: TestableLooper
+
+ private var commandCaptor = argumentCaptor<SessionCommand>()
+ private var runnableCaptor = argumentCaptor<Runnable>()
+
+ private val legacyToken = MediaSession.Token(1, null)
+ private val token = mock<SessionToken>()
+ private val handler =
+ mock<Handler> {
+ on { post(runnableCaptor.capture()) } doAnswer
+ {
+ runnableCaptor.lastValue.run()
+ true
+ }
+ }
+ private val customLayout = ImmutableList.of<CommandButton>()
+ private val media3Controller =
+ mock<Media3Controller> {
+ on { customLayout } doReturn customLayout
+ on { sessionExtras } doReturn Bundle()
+ on { isCommandAvailable(any()) } doReturn true
+ on { isSessionCommandAvailable(any<SessionCommand>()) } doReturn true
+ }
+
+ private lateinit var underTest: Media3ActionFactory
+
+ @Before
+ fun setup() {
+ testableLooper = TestableLooper.get(this)
+
+ underTest =
+ Media3ActionFactory(
+ context,
+ kosmos.imageLoader,
+ controllerFactory,
+ tokenFactory,
+ kosmos.mediaLogger,
+ kosmos.looper,
+ handler,
+ kosmos.testScope,
+ )
+
+ controllerFactory.setMedia3Controller(media3Controller)
+ tokenFactory.setMedia3SessionToken(token)
+ }
+
+ @Test
+ fun media3Actions_playingState_withCustomActions() =
+ testScope.runTest {
+ // Media is playing, all commands available, with custom actions
+ val customLayout = ImmutableList.copyOf((0..1).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_pause))
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).pause()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.prevOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_prev))
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToPrevious()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_next))
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).seekToNext()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_pausedState_hasPauseAction() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_play))
+ clearInvocations(media3Controller)
+
+ actions.playOrPause!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).play()
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+ }
+
+ @Test
+ fun media3Actions_bufferingState_hasLoadingSpinner() =
+ testScope.runTest {
+ whenever(media3Controller.isPlaying).thenReturn(false)
+ whenever(media3Controller.playbackState).thenReturn(Player.STATE_BUFFERING)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+ assertThat(actions.playOrPause!!.contentDescription)
+ .isEqualTo(context.getString(R.string.controls_media_button_connecting))
+ assertThat(actions.playOrPause!!.action).isNull()
+ assertThat(actions.playOrPause!!.rebindId)
+ .isEqualTo(com.android.internal.R.drawable.progress_small_material)
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_usesCustom() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.prevOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.nextOrCustom!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.nextOrCustom!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 2")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ testableLooper.processAllMessages()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 2")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 3")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 3")
+ verify(media3Controller).release()
+ }
+
+ @Test
+ fun media3Actions_noPrevNext_reservedSpace() =
+ testScope.runTest {
+ val customLayout = ImmutableList.copyOf((0..4).map { createCustomCommandButton(it) })
+ whenever(media3Controller.customLayout).thenReturn(customLayout)
+ whenever(media3Controller.isPlaying).thenReturn(true)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_PREVIOUS)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(
+ eq(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ )
+ )
+ .thenReturn(false)
+ whenever(media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT)))
+ .thenReturn(false)
+ whenever(
+ media3Controller.isCommandAvailable(eq(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM))
+ )
+ .thenReturn(false)
+ val extras =
+ Bundle().apply {
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ true,
+ )
+ putBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ true,
+ )
+ }
+ whenever(media3Controller.sessionExtras).thenReturn(extras)
+ val result = getActions()
+
+ assertThat(result).isNotNull()
+ val actions = result!!
+
+ assertThat(actions.prevOrCustom).isNull()
+ assertThat(actions.nextOrCustom).isNull()
+
+ assertThat(actions.custom0!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 0")
+ actions.custom0!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 0")
+ verify(media3Controller).release()
+ clearInvocations(media3Controller)
+
+ assertThat(actions.custom1!!.contentDescription).isEqualTo("$CUSTOM_ACTION_NAME 1")
+ actions.custom1!!.action!!.run()
+ runCurrent()
+ verify(media3Controller).sendCustomCommand(commandCaptor.capture(), any<Bundle>())
+ assertThat(commandCaptor.lastValue.customAction).isEqualTo("$CUSTOM_ACTION_COMMAND 1")
+ verify(media3Controller).release()
+ }
+
+ private suspend fun getActions(): MediaButton? {
+ val result = underTest.createActionsFromSession(PACKAGE_NAME, legacyToken)
+ testScope.runCurrent()
+ verify(media3Controller).release()
+
+ // Clear so tests can verify the correct number of release() calls in later operations
+ clearInvocations(media3Controller)
+ return result
+ }
+
+ private fun createCustomCommandButton(id: Int): CommandButton {
+ return CommandButton.Builder()
+ .setDisplayName("$CUSTOM_ACTION_NAME $id")
+ .setSessionCommand(SessionCommand("$CUSTOM_ACTION_COMMAND $id", Bundle()))
+ .build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
index fc9e595945dd..1a7265b09aae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderTest.kt
@@ -29,6 +29,7 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.service.notification.StatusBarNotification
+import android.testing.TestableLooper.RunWithLooper
import androidx.media.utils.MediaConstants
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
@@ -69,6 +70,7 @@ private const val SESSION_TITLE = "title"
private const val SESSION_EMPTY_TITLE = ""
@SmallTest
+@RunWithLooper
@RunWith(AndroidJUnit4::class)
class MediaDataLoaderTest : SysuiTestCase() {
@@ -80,6 +82,7 @@ class MediaDataLoaderTest : SysuiTestCase() {
private val fakeFeatureFlags = kosmos.fakeFeatureFlagsClassic
private val mediaFlags = kosmos.mediaFlags
private val mediaControllerFactory = kosmos.fakeMediaControllerFactory
+ private val media3ActionFactory = kosmos.media3ActionFactory
private val session = MediaSession(context, "MediaDataLoaderTestSession")
private val metadataBuilder =
MediaMetadata.Builder().apply {
@@ -87,21 +90,25 @@ class MediaDataLoaderTest : SysuiTestCase() {
putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
}
- private val underTest: MediaDataLoader =
- MediaDataLoader(
- context,
- testDispatcher,
- testScope,
- mediaControllerFactory,
- mediaFlags,
- kosmos.imageLoader,
- statusBarManager,
- )
+ private lateinit var underTest: MediaDataLoader
@Before
fun setUp() {
mediaControllerFactory.setControllerForToken(session.sessionToken, mediaController)
+ whenever(mediaController.sessionToken).thenReturn(session.sessionToken)
whenever(mediaController.metadata).then { metadataBuilder.build() }
+
+ underTest =
+ MediaDataLoader(
+ context,
+ testDispatcher,
+ testScope,
+ mediaControllerFactory,
+ mediaFlags,
+ kosmos.imageLoader,
+ statusBarManager,
+ kosmos.media3ActionFactory,
+ )
}
@Test
@@ -394,6 +401,7 @@ class MediaDataLoaderTest : SysuiTestCase() {
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
@@ -422,6 +430,7 @@ class MediaDataLoaderTest : SysuiTestCase() {
mediaFlags,
mockImageLoader,
statusBarManager,
+ media3ActionFactory,
)
metadataBuilder.putString(
MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index e12c67b24893..104aa517fa4f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -16,7 +16,7 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo
import com.android.wm.shell.shared.split.SplitBounds
import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50
import com.google.common.truth.Truth.assertThat
@@ -219,9 +219,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
}
@Suppress("UNCHECKED_CAST")
- private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+ private fun givenRecentTasks(vararg tasks: GroupedTaskInfo) {
whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
- val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+ val consumer = it.arguments.last() as Consumer<List<GroupedTaskInfo>>
consumer.accept(tasks.toList())
}
}
@@ -247,7 +247,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
userId: Int = 0,
isVisible: Boolean = false,
userType: RecentTask.UserType = STANDARD,
- ): GroupedRecentTaskInfo {
+ ): GroupedTaskInfo {
val userInfo =
mock<UserInfo> {
whenever(isCloneProfile).thenReturn(userType == CLONED)
@@ -255,7 +255,7 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
whenever(isPrivateProfile).thenReturn(userType == PRIVATE)
}
whenever(userManager.getUserInfo(userId)).thenReturn(userInfo)
- return GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId, userId, isVisible))
+ return GroupedTaskInfo.forFullscreenTasks(createTaskInfo(taskId, userId, isVisible))
}
private fun createTaskPair(
@@ -263,9 +263,9 @@ class ShellRecentTaskListProviderTest : SysuiTestCase() {
userId1: Int = 0,
taskId2: Int,
userId2: Int = 0,
- isVisible: Boolean = false
- ): GroupedRecentTaskInfo =
- GroupedRecentTaskInfo.forSplitTasks(
+ isVisible: Boolean = false,
+ ): GroupedTaskInfo =
+ GroupedTaskInfo.forSplitTasks(
createTaskInfo(taskId1, userId1, isVisible),
createTaskInfo(taskId2, userId2, isVisible),
SplitBounds(Rect(), Rect(), taskId1, taskId2, SNAP_TO_2_50_50)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
index 2905a7329d21..646722bee35f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/navigationbar/views/NavigationBarTest.java
@@ -116,6 +116,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
@@ -208,7 +209,7 @@ public class NavigationBarTest extends SysuiTestCase {
@Mock
private LightBarController mLightBarController;
@Mock
- private LightBarController.Factory mLightBarcontrollerFactory;
+ private LightBarControllerStore mLightBarControllerStore;
@Mock
private AutoHideController mAutoHideController;
@Mock
@@ -257,7 +258,7 @@ public class NavigationBarTest extends SysuiTestCase {
public void setup() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController);
+ when(mLightBarControllerStore.forDisplay(anyInt())).thenReturn(mLightBarController);
when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController);
when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton);
when(mNavigationBarView.getRecentsButton()).thenReturn(mRecentsButton);
@@ -649,8 +650,7 @@ public class NavigationBarTest extends SysuiTestCase {
mFakeExecutor,
mUiEventLogger,
mNavBarHelper,
- mLightBarController,
- mLightBarcontrollerFactory,
+ mLightBarControllerStore,
mAutoHideController,
mAutoHideControllerFactory,
Optional.of(mTelecomManager),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
index ff40e43e2c8c..a06353171c33 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/logging/QSTileLoggerTest.kt
@@ -115,7 +115,7 @@ class QSTileLoggerTest : SysuiTestCase() {
underTest.logUserActionPipeline(
TileSpec.create("test_spec"),
QSTileUserAction.Click(null),
- QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -141,7 +141,7 @@ class QSTileLoggerTest : SysuiTestCase() {
fun testLogStateUpdate() {
underTest.logStateUpdate(
TileSpec.create("test_spec"),
- QSTileState.build({ Icon.Resource(0, ContentDescription.Resource(0)) }, "") {},
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), "") {},
"test_data",
)
@@ -162,18 +162,14 @@ class QSTileLoggerTest : SysuiTestCase() {
@Test
fun testLogForceUpdate() {
- underTest.logForceUpdate(
- TileSpec.create("test_spec"),
- )
+ underTest.logForceUpdate(TileSpec.create("test_spec"))
assertThat(logBuffer.getStringBuffer()).contains("tile data force update")
}
@Test
fun testLogInitialUpdate() {
- underTest.logInitialRequest(
- TileSpec.create("test_spec"),
- )
+ underTest.logInitialRequest(TileSpec.create("test_spec"))
assertThat(logBuffer.getStringBuffer()).contains("tile data initial update")
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index c918ed82604c..056efb34a0b1 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -85,8 +85,8 @@ class QSTileViewModelImplTest : SysuiTestCase() {
object : QSTileDataToStateMapper<Any> {
override fun map(config: QSTileConfig, data: Any): QSTileState =
QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data.toString()
+ Icon.Resource(0, ContentDescription.Resource(0)),
+ data.toString(),
) {}
}
},
@@ -116,7 +116,7 @@ class QSTileViewModelImplTest : SysuiTestCase() {
.isEqualTo(
"test_spec:\n" +
" QSTileState(" +
- "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
+ "icon=Resource(res=0, contentDescription=Resource(res=0)), " +
"iconRes=null, " +
"label=test_data, " +
"activationState=INACTIVE, " +
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
index 5a73fe28ee18..00460bfe83b2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/AirplaneModeMapperTest.kt
@@ -66,7 +66,7 @@ class AirplaneModeMapperTest : SysuiTestCase() {
createAirplaneModeState(
QSTileState.ActivationState.ACTIVE,
context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_ACTIVE],
- R.drawable.qs_airplane_icon_on
+ R.drawable.qs_airplane_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -81,7 +81,7 @@ class AirplaneModeMapperTest : SysuiTestCase() {
createAirplaneModeState(
QSTileState.ActivationState.INACTIVE,
context.resources.getStringArray(R.array.tile_states_airplane)[Tile.STATE_INACTIVE],
- R.drawable.qs_airplane_icon_off
+ R.drawable.qs_airplane_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -89,11 +89,11 @@ class AirplaneModeMapperTest : SysuiTestCase() {
private fun createAirplaneModeState(
activationState: QSTileState.ActivationState,
secondaryLabel: String,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.airplane_mode)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -103,7 +103,7 @@ class AirplaneModeMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 79e4fef874b6..632aae035ede 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -51,7 +51,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
.apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
.resources,
context.theme,
- fakeClock
+ fakeClock,
)
}
@@ -69,7 +69,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val expectedState =
createAlarmTileState(
QSTileState.ActivationState.INACTIVE,
- context.getString(R.string.qs_alarm_tile_no_alarm)
+ context.getString(R.string.qs_alarm_tile_no_alarm),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -85,7 +85,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter24Hour.format(localDateTime)
val expectedState =
@@ -104,7 +104,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
val expectedState =
@@ -124,7 +124,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
val expectedState =
@@ -144,7 +144,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatter12Hour.format(localDateTime)
val expectedState =
@@ -164,7 +164,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
val localDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val expectedSecondaryLabel = AlarmTileMapper.formatterDateOnly.format(localDateTime)
val expectedState =
@@ -174,11 +174,11 @@ class AlarmTileMapperTest : SysuiTestCase() {
private fun createAlarmTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String
+ secondaryLabel: String,
): QSTileState {
val label = context.getString(R.string.status_bar_alarm)
return QSTileState(
- { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null),
R.drawable.ic_alarm,
label,
activationState,
@@ -188,7 +188,7 @@ class AlarmTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
index a0d26c28cbfa..5385f945946c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapperTest.kt
@@ -253,7 +253,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.battery_detail_switch_title)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -265,7 +265,7 @@ class BatterySaverTileMapperTest : SysuiTestCase() {
stateDescription,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
index ea7b7c5f797d..356b98eb192e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -45,7 +45,7 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() {
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
)
}
@@ -73,11 +73,11 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() {
private fun createColorCorrectionTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String
+ secondaryLabel: String,
): QSTileState {
val label = context.getString(R.string.quick_settings_color_correction_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null),
R.drawable.ic_qs_color_correction,
label,
activationState,
@@ -87,7 +87,7 @@ class ColorCorrectionTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
index f1d08c068150..8236c4c1e638 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileMapperTest.kt
@@ -57,10 +57,7 @@ class CustomTileMapperTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply { customTileSpec = TileSpec.Companion.create(TEST_COMPONENT) }
private val underTest by lazy {
- CustomTileMapper(
- context = mockContext,
- uriGrantsManager = uriGrantsManager,
- )
+ CustomTileMapper(context = mockContext, uriGrantsManager = uriGrantsManager)
}
@Test
@@ -68,10 +65,7 @@ class CustomTileMapperTest : SysuiTestCase() {
with(kosmos) {
testScope.runTest {
val actual =
- underTest.map(
- customTileQsTileConfig,
- createModel(hasPendingBind = true),
- )
+ underTest.map(customTileQsTileConfig, createModel(hasPendingBind = true))
val expected =
createTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
@@ -91,10 +85,7 @@ class CustomTileMapperTest : SysuiTestCase() {
customTileQsTileConfig,
createModel(tileState = Tile.STATE_ACTIVE),
)
- val expected =
- createTileState(
- activationState = QSTileState.ActivationState.ACTIVE,
- )
+ val expected = createTileState(activationState = QSTileState.ActivationState.ACTIVE)
assertThat(actual).isEqualTo(expected)
}
@@ -110,9 +101,7 @@ class CustomTileMapperTest : SysuiTestCase() {
createModel(tileState = Tile.STATE_INACTIVE),
)
val expected =
- createTileState(
- activationState = QSTileState.ActivationState.INACTIVE,
- )
+ createTileState(activationState = QSTileState.ActivationState.INACTIVE)
assertThat(actual).isEqualTo(expected)
}
@@ -142,10 +131,7 @@ class CustomTileMapperTest : SysuiTestCase() {
with(kosmos) {
testScope.runTest {
val actual =
- underTest.map(
- customTileQsTileConfig,
- createModel(isToggleable = false),
- )
+ underTest.map(customTileQsTileConfig, createModel(isToggleable = false))
val expected =
createTileState(
sideIcon = QSTileState.SideViewIcon.Chevron,
@@ -184,7 +170,7 @@ class CustomTileMapperTest : SysuiTestCase() {
customTileQsTileConfig,
createModel(
tileIcon = createIcon(RuntimeException(), false),
- defaultTileIcon = createIcon(null, true)
+ defaultTileIcon = createIcon(null, true),
),
)
val expected =
@@ -266,7 +252,7 @@ class CustomTileMapperTest : SysuiTestCase() {
a11yClass: String? = Switch::class.qualifiedName,
): QSTileState {
return QSTileState(
- { icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) } },
+ icon?.let { com.android.systemui.common.shared.model.Icon.Loaded(icon, null) },
null,
"test label",
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 63fb67d432f0..587585ccee2e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -44,7 +44,7 @@ class FlashlightMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -74,7 +74,7 @@ class FlashlightMapperTest : SysuiTestCase() {
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -85,7 +85,7 @@ class FlashlightMapperTest : SysuiTestCase() {
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -96,7 +96,7 @@ class FlashlightMapperTest : SysuiTestCase() {
val expectedIcon =
Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
index f8e01be5163f..e81771ec38d5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapperTest.kt
@@ -42,7 +42,7 @@ class FontScalingTileMapperTest : SysuiTestCase() {
context.orCreateTestableResources
.apply { addOverride(R.drawable.ic_qs_font_scaling, TestStubDrawable()) }
.resources,
- context.theme
+ context.theme,
)
}
@@ -58,14 +58,7 @@ class FontScalingTileMapperTest : SysuiTestCase() {
private fun createFontScalingTileState(): QSTileState =
QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(
- R.drawable.ic_qs_font_scaling,
- )!!,
- null
- )
- },
+ Icon.Loaded(context.getDrawable(R.drawable.ic_qs_font_scaling)!!, null),
R.drawable.ic_qs_font_scaling,
context.getString(R.string.quick_settings_font_scaling_label),
QSTileState.ActivationState.ACTIVE,
@@ -75,6 +68,6 @@ class FontScalingTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
index cdf6bda91301..12d604ff6a7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt
@@ -102,7 +102,7 @@ class HearingDevicesTileMapperTest : SysuiTestCase() {
val label = context.getString(R.string.quick_settings_hearing_devices_label)
val iconRes = R.drawable.qs_hearing_devices_icon
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
index d32ba47204c0..9dcf49e02697 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapperTest.kt
@@ -187,7 +187,7 @@ class InternetTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_internet_label)
return QSTileState(
- { icon },
+ icon,
iconRes,
label,
activationState,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
index a7bd69770a4f..30fce73e04da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapperTest.kt
@@ -49,7 +49,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_invert_colors_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -63,7 +63,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
createColorInversionTileState(
QSTileState.ActivationState.INACTIVE,
subtitleArray[1],
- R.drawable.qs_invert_colors_icon_off
+ R.drawable.qs_invert_colors_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -78,7 +78,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
createColorInversionTileState(
QSTileState.ActivationState.ACTIVE,
subtitleArray[2],
- R.drawable.qs_invert_colors_icon_on
+ R.drawable.qs_invert_colors_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -90,7 +90,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_inversion_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -100,7 +100,7 @@ class ColorInversionTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index ea74a4c0d398..37e8a6053682 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -45,7 +45,7 @@ class LocationTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -70,7 +70,7 @@ class LocationTileMapperTest : SysuiTestCase() {
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
@@ -79,7 +79,7 @@ class LocationTileMapperTest : SysuiTestCase() {
val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
- val actualIcon = tileState.icon()
+ val actualIcon = tileState.icon
Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
index c3d45dbbd09a..4e91d16bf1ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapperTest.kt
@@ -59,34 +59,24 @@ class ModesTileMapperTest : SysuiTestCase() {
@Test
fun inactiveState() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = false,
- activeModes = emptyList(),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = false, activeModes = emptyList(), icon = icon)
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.INACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("No active modes")
}
@Test
fun activeState_oneMode() {
val icon = TestStubDrawable("res123").asIcon()
- val model =
- ModesTileModel(
- isActivated = true,
- activeModes = listOf("DND"),
- icon = icon,
- )
+ val model = ModesTileModel(isActivated = true, activeModes = listOf("DND"), icon = icon)
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("DND is active")
}
@@ -103,7 +93,7 @@ class ModesTileMapperTest : SysuiTestCase() {
val state = underTest.map(config, model)
assertThat(state.activationState).isEqualTo(QSTileState.ActivationState.ACTIVE)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.secondaryLabel).isEqualTo("3 modes are active")
}
@@ -115,12 +105,12 @@ class ModesTileMapperTest : SysuiTestCase() {
isActivated = false,
activeModes = emptyList(),
icon = icon,
- iconResId = 123
+ iconResId = 123,
)
val state = underTest.map(config, model)
- assertThat(state.icon()).isEqualTo(icon)
+ assertThat(state.icon).isEqualTo(icon)
assertThat(state.iconRes).isEqualTo(123)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
index 75273f2a52e1..1457f533f5ec 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapperTest.kt
@@ -73,7 +73,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -88,7 +88,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_INACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -102,7 +102,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -116,7 +116,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE]
+ context.resources.getStringArray(R.array.tile_states_night)[Tile.STATE_ACTIVE],
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -140,7 +140,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.ACTIVE,
- context.getString(R.string.quick_settings_night_secondary_label_until_sunrise)
+ context.getString(R.string.quick_settings_night_secondary_label_until_sunrise),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -154,7 +154,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
val expectedState =
createNightDisplayTileState(
QSTileState.ActivationState.INACTIVE,
- context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset)
+ context.getString(R.string.quick_settings_night_secondary_label_on_at_sunset),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -181,8 +181,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter24Hour.format(testStartTime)
- )
+ formatter24Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -199,8 +199,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter12Hour.format(testStartTime)
- )
+ formatter12Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -218,8 +218,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.INACTIVE,
context.getString(
R.string.quick_settings_night_secondary_label_on_at,
- formatter12Hour.format(testStartTime)
- )
+ formatter12Hour.format(testStartTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -235,8 +235,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter24Hour.format(testEndTime)
- )
+ formatter24Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -252,8 +252,8 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter12Hour.format(testEndTime)
- )
+ formatter12Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -270,15 +270,15 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.ACTIVE,
context.getString(
R.string.quick_settings_secondary_label_until,
- formatter24Hour.format(testEndTime)
- )
+ formatter24Hour.format(testEndTime),
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
private fun createNightDisplayTileState(
activationState: QSTileState.ActivationState,
- secondaryLabel: String?
+ secondaryLabel: String?,
): QSTileState {
val label = context.getString(R.string.quick_settings_night_display_label)
val iconRes =
@@ -289,7 +289,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -299,7 +299,7 @@ class NightDisplayTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
index 3189a9e063a1..7782d2b279a8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapperTest.kt
@@ -51,11 +51,11 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
.apply {
addOverride(
com.android.internal.R.drawable.ic_qs_one_handed_mode,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -69,7 +69,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
createOneHandedModeTileState(
QSTileState.ActivationState.INACTIVE,
subtitleArray[1],
- com.android.internal.R.drawable.ic_qs_one_handed_mode
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -84,7 +84,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
createOneHandedModeTileState(
QSTileState.ActivationState.ACTIVE,
subtitleArray[2],
- com.android.internal.R.drawable.ic_qs_one_handed_mode
+ com.android.internal.R.drawable.ic_qs_one_handed_mode,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -96,7 +96,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(R.string.quick_settings_onehanded_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -106,7 +106,7 @@ class OneHandedModeTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
index 08e5cbef31ab..ed33250a3392 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapperTest.kt
@@ -49,11 +49,11 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
.apply {
addOverride(
com.android.systemui.res.R.drawable.ic_qr_code_scanner,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -64,11 +64,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
val outputState = mapper.map(config, inputModel)
- val expectedState =
- createQRCodeScannerTileState(
- QSTileState.ActivationState.INACTIVE,
- null,
- )
+ val expectedState = createQRCodeScannerTileState(QSTileState.ActivationState.INACTIVE, null)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -83,7 +79,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
QSTileState.ActivationState.UNAVAILABLE,
context.getString(
com.android.systemui.res.R.string.qr_code_scanner_updating_secondary_label
- )
+ ),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -94,12 +90,10 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
): QSTileState {
val label = context.getString(com.android.systemui.res.R.string.qr_code_scanner_title)
return QSTileState(
- {
- Icon.Loaded(
- context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
- null
- )
- },
+ Icon.Loaded(
+ context.getDrawable(com.android.systemui.res.R.drawable.ic_qr_code_scanner)!!,
+ null,
+ ),
com.android.systemui.res.R.drawable.ic_qr_code_scanner,
label,
activationState,
@@ -109,7 +103,7 @@ class QRCodeScannerTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.Chevron,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
index ca30e9ca3e69..85111fd07663 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapperTest.kt
@@ -51,7 +51,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_extra_dim_icon_off, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -61,10 +61,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
val outputState = mapper.map(config, inputModel)
- val expectedState =
- createReduceBrightColorsTileState(
- QSTileState.ActivationState.INACTIVE,
- )
+ val expectedState = createReduceBrightColorsTileState(QSTileState.ActivationState.INACTIVE)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -79,7 +76,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
}
private fun createReduceBrightColorsTileState(
- activationState: QSTileState.ActivationState,
+ activationState: QSTileState.ActivationState
): QSTileState {
val label =
context.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
@@ -88,7 +85,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
R.drawable.qs_extra_dim_icon_on
else R.drawable.qs_extra_dim_icon_off
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -101,7 +98,7 @@ class ReduceBrightColorsTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
index 3e40c5ca797c..53671ba38eb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapperTest.kt
@@ -66,13 +66,13 @@ class RotationLockTileMapperTest : SysuiTestCase() {
addOverride(com.android.internal.R.bool.config_allowRotationResolver, true)
addOverride(
com.android.internal.R.array.config_foldedDeviceStates,
- intArrayOf() // empty array <=> device is not foldable
+ intArrayOf(), // empty array <=> device is not foldable
)
}
.resources,
context.theme,
devicePostureController,
- deviceStateManager
+ deviceStateManager,
)
}
@@ -86,7 +86,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
createRotationLockTileState(
QSTileState.ActivationState.ACTIVE,
EMPTY_SECONDARY_STRING,
- R.drawable.qs_auto_rotate_icon_on
+ R.drawable.qs_auto_rotate_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -101,7 +101,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
createRotationLockTileState(
QSTileState.ActivationState.ACTIVE,
context.getString(R.string.rotation_lock_camera_rotation_on),
- R.drawable.qs_auto_rotate_icon_on
+ R.drawable.qs_auto_rotate_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -116,7 +116,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
createRotationLockTileState(
QSTileState.ActivationState.INACTIVE,
EMPTY_SECONDARY_STRING,
- R.drawable.qs_auto_rotate_icon_off
+ R.drawable.qs_auto_rotate_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -167,7 +167,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
mapper.apply {
overrideResource(
com.android.internal.R.array.config_foldedDeviceStates,
- intArrayOf(1, 2, 3)
+ intArrayOf(1, 2, 3),
)
}
whenever(deviceStateManager.supportedDeviceStates).thenReturn(kosmos.foldedDeviceStateList)
@@ -176,11 +176,11 @@ class RotationLockTileMapperTest : SysuiTestCase() {
private fun createRotationLockTileState(
activationState: QSTileState.ActivationState,
secondaryLabel: String,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.quick_settings_rotation_unlocked_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -190,7 +190,7 @@ class RotationLockTileMapperTest : SysuiTestCase() {
secondaryLabel,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index 9bb61415de28..9a450653aa8f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -46,7 +46,7 @@ class DataSaverTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -59,7 +59,7 @@ class DataSaverTileMapperTest : SysuiTestCase() {
val expectedState =
createDataSaverTileState(
QSTileState.ActivationState.ACTIVE,
- R.drawable.qs_data_saver_icon_on
+ R.drawable.qs_data_saver_icon_on,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -73,14 +73,14 @@ class DataSaverTileMapperTest : SysuiTestCase() {
val expectedState =
createDataSaverTileState(
QSTileState.ActivationState.INACTIVE,
- R.drawable.qs_data_saver_icon_off
+ R.drawable.qs_data_saver_icon_off,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
private fun createDataSaverTileState(
activationState: QSTileState.ActivationState,
- iconRes: Int
+ iconRes: Int,
): QSTileState {
val label = context.getString(R.string.data_saver)
val secondaryLabel =
@@ -91,7 +91,7 @@ class DataSaverTileMapperTest : SysuiTestCase() {
else context.resources.getStringArray(R.array.tile_states_saver)[0]
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -101,7 +101,7 @@ class DataSaverTileMapperTest : SysuiTestCase() {
null,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
index 336b56612261..cd683c44a59c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/screenrecord/ui/ScreenRecordTileMapperTest.kt
@@ -52,7 +52,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_screen_record_icon_off, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -82,7 +82,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
createScreenRecordTileState(
QSTileState.ActivationState.ACTIVE,
R.drawable.qs_screen_record_icon_on,
- String.format("%d...", timeLeft)
+ String.format("%d...", timeLeft),
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -110,7 +110,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
val label = context.getString(R.string.quick_settings_screen_record_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -123,7 +123,7 @@ class ScreenRecordTileMapperTest : SysuiTestCase() {
QSTileState.SideViewIcon.Chevron
else QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
index b08f39b0accf..c569403960d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt
@@ -56,7 +56,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_camera_mic_available),
R.drawable.qs_camera_access_icon_on,
null,
- CAMERA
+ CAMERA,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -74,7 +74,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_camera_mic_blocked),
R.drawable.qs_camera_access_icon_off,
null,
- CAMERA
+ CAMERA,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -92,7 +92,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_camera_mic_available),
R.drawable.qs_mic_access_on,
null,
- MICROPHONE
+ MICROPHONE,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -110,7 +110,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
context.getString(R.string.quick_settings_camera_mic_blocked),
R.drawable.qs_mic_access_off,
null,
- MICROPHONE
+ MICROPHONE,
)
QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
}
@@ -146,7 +146,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
else context.getString(R.string.quick_settings_mic_label)
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -156,7 +156,7 @@ class SensorPrivacyToggleTileMapperTest : SysuiTestCase() {
stateDescription,
QSTileState.SideViewIcon.None,
QSTileState.EnabledState.ENABLED,
- Switch::class.qualifiedName
+ Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index c021caa598b9..0d2ebe42b7ad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -49,7 +49,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
}
.resources,
- context.theme
+ context.theme,
)
}
@@ -69,7 +69,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
expandedAccessibilityClass: KClass<out View>? = Switch::class,
): QSTileState {
return QSTileState(
- { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes,
label,
activationState,
@@ -79,7 +79,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
stateDescription,
sideViewIcon,
enabledState,
- expandedAccessibilityClass?.qualifiedName
+ expandedAccessibilityClass?.qualifiedName,
)
}
@@ -98,7 +98,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
createUiNightModeTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedContentDescription
+ contentDescription = expectedContentDescription,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -118,7 +118,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
createUiNightModeTileState(
activationState = QSTileState.ActivationState.UNAVAILABLE,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedContentDescription
+ contentDescription = expectedContentDescription,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -136,7 +136,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.INACTIVE,
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -155,7 +155,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.ACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -174,7 +174,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.ACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -193,7 +193,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
label = expectedLabel,
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.INACTIVE,
- contentDescription = expectedLabel
+ contentDescription = expectedLabel,
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -214,7 +214,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -237,7 +237,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = expectedContentDescription,
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -258,7 +258,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -279,7 +279,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -300,7 +300,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -312,7 +312,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
nightMode = true,
powerSave = false,
isLocationEnabled = true,
- uiMode = UiModeManager.MODE_NIGHT_AUTO
+ uiMode = UiModeManager.MODE_NIGHT_AUTO,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -328,7 +328,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -340,7 +340,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
nightMode = false,
powerSave = false,
isLocationEnabled = true,
- uiMode = UiModeManager.MODE_NIGHT_AUTO
+ uiMode = UiModeManager.MODE_NIGHT_AUTO,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -356,7 +356,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = TextUtils.concat(expectedLabel, ", ", expectedSecondaryLabel),
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -379,7 +379,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -401,7 +401,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -413,7 +413,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
nightMode = false,
powerSave = false,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -428,7 +428,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.INACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -440,7 +440,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
nightMode = true,
powerSave = false,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -455,7 +455,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
activationState = QSTileState.ActivationState.ACTIVE,
contentDescription = expectedLabel,
supportedActions =
- setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
@@ -467,7 +467,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
nightMode = false,
powerSave = true,
uiMode = UiModeManager.MODE_NIGHT_CUSTOM,
- nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN
+ nighModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN,
)
val actualState: QSTileState = mapper.map(qsTileConfig, inputModel)
@@ -484,7 +484,7 @@ class UiModeNightTileMapperTest : SysuiTestCase() {
secondaryLabel = expectedSecondaryLabel,
activationState = QSTileState.ActivationState.UNAVAILABLE,
contentDescription = expectedContentDescription,
- supportedActions = setOf(QSTileState.UserAction.LONG_CLICK)
+ supportedActions = setOf(QSTileState.UserAction.LONG_CLICK),
)
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
index e7bde681fe6f..86321ea04703 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapperTest.kt
@@ -56,7 +56,7 @@ class WorkModeTileMapperTest : SysuiTestCase() {
whenever(
devicePolicyResourceManager.getString(
eq(DevicePolicyResources.Strings.SystemUi.QS_WORK_PROFILE_LABEL),
- any()
+ any(),
)
)
.thenReturn(testLabel)
@@ -66,12 +66,12 @@ class WorkModeTileMapperTest : SysuiTestCase() {
.apply {
addOverride(
com.android.internal.R.drawable.stat_sys_managed_profile_status,
- TestStubDrawable()
+ TestStubDrawable(),
)
}
.resources,
context.theme,
- devicePolicyManager
+ devicePolicyManager,
)
}
@@ -105,13 +105,11 @@ class WorkModeTileMapperTest : SysuiTestCase() {
QSTileStateSubject.assertThat(actualState).isEqualTo(expectedState)
}
- private fun createWorkModeTileState(
- activationState: QSTileState.ActivationState,
- ): QSTileState {
+ private fun createWorkModeTileState(activationState: QSTileState.ActivationState): QSTileState {
val label = testLabel
val iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
return QSTileState(
- icon = { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
+ icon = Icon.Loaded(context.getDrawable(iconRes)!!, null),
iconRes = iconRes,
label = label,
activationState = activationState,
@@ -134,7 +132,7 @@ class WorkModeTileMapperTest : SysuiTestCase() {
stateDescription = null,
sideViewIcon = QSTileState.SideViewIcon.None,
enabledState = QSTileState.EnabledState.ENABLED,
- expandedAccessibilityClassName = Switch::class.qualifiedName
+ expandedAccessibilityClassName = Switch::class.qualifiedName,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
index c33e2a49ef5d..954215eede0d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelTest.kt
@@ -184,10 +184,7 @@ class QSTileViewModelTest : SysuiTestCase() {
{
object : QSTileDataToStateMapper<String> {
override fun map(config: QSTileConfig, data: String): QSTileState =
- QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data
- ) {}
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
}
},
disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
index 7955f2fc1335..0219a4c58dcf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelUserInputTest.kt
@@ -104,7 +104,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
eq(tileConfig.tileSpec),
eq(userAction),
any(),
- eq("initial_data")
+ eq("initial_data"),
)
verify(qsTileAnalytics).trackUserAction(eq(tileConfig), eq(userAction))
}
@@ -130,7 +130,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -159,7 +159,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -174,7 +174,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
QSTilePolicy.Restricted(
listOf(
DISABLED_RESTRICTION,
- FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2
+ FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2,
)
)
}
@@ -194,13 +194,13 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(DISABLED_RESTRICTION)
+ eq(DISABLED_RESTRICTION),
)
verify(qsTileLogger, never())
.logUserActionRejectedByPolicy(
eq(userAction),
eq(tileConfig.tileSpec),
- eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2)
+ eq(FakeDisabledByPolicyInteractor.DISABLED_RESTRICTION_2),
)
verify(qsTileAnalytics, never()).trackUserAction(any(), any())
}
@@ -243,10 +243,7 @@ class QSTileViewModelUserInputTest : SysuiTestCase() {
{
object : QSTileDataToStateMapper<String> {
override fun map(config: QSTileConfig, data: String): QSTileState =
- QSTileState.build(
- { Icon.Resource(0, ContentDescription.Resource(0)) },
- data
- ) {}
+ QSTileState.build(Icon.Resource(0, ContentDescription.Resource(0)), data) {}
}
},
disabledByPolicyInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 319f1e577cd9..3be8a380b191 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -44,6 +44,7 @@ import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.ui.viewmodel.lockscreenUserActionsViewModel
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.collectLastValue
import com.android.systemui.kosmos.runCurrent
import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.testScope
@@ -153,7 +154,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
kosmos.runTest {
- val actions by testScope.collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(kosmos.lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -170,7 +171,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -180,7 +181,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
kosmos.runTest {
- val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
+ val actions by collectLastValue(shadeUserActionsViewModel.actions)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
assertCurrentScene(Scenes.Lockscreen)
@@ -197,8 +198,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
@Test
fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
kosmos.runTest {
- val actions by testScope.collectLastValue(shadeUserActionsViewModel.actions)
- val canSwipeToEnter by testScope.collectLastValue(deviceEntryInteractor.canSwipeToEnter)
+ val actions by collectLastValue(shadeUserActionsViewModel.actions)
+ val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter)
setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true)
@@ -279,7 +280,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
kosmos.runTest {
unlockDevice()
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Gone)
@@ -302,7 +303,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun dismissingIme_whileOnPasswordBouncer_navigatesToLockscreen() =
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
@@ -319,14 +320,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
fun bouncerActionButtonClick_opensEmergencyServicesDialer() =
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by
- testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible")
.that(bouncerActionButton)
.isNotNull()
@@ -341,14 +341,13 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
kosmos.runTest {
setAuthMethod(AuthenticationMethodModel.Password)
startPhoneCall()
- val actions by testScope.collectLastValue(lockscreenUserActionsViewModel.actions)
+ val actions by collectLastValue(lockscreenUserActionsViewModel.actions)
val upDestinationSceneKey =
(actions?.get(Swipe.Up) as? UserActionResult.ChangeScene)?.toScene
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by
- testScope.collectLastValue(bouncerSceneContentViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible during call")
.that(bouncerActionButton)
.isNotNull()
@@ -574,7 +573,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
val authMethodViewModel by
- testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
@@ -603,7 +602,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
val authMethodViewModel by
- testScope.collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index bfe5ef7acbce..db2297c99315 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -32,7 +32,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
@@ -69,7 +69,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
private val notificationPresenter = mock<NotificationPresenter>()
private val notificationsController = mock<NotificationsController>()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
- private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsRepository = kosmos.activeNotificationListRepository
private val activeNotificationsInteractor =
ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 2c8f7cf47723..55f88cc5b7a2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -2119,40 +2119,6 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
- fun switchToGone_whenSurfaceBehindLockscreenVisibleMidTransition() =
- testScope.runTest {
- val currentScene by collectLastValue(sceneInteractor.currentScene)
- val transitionStateFlow =
- prepareState(authenticationMethod = AuthenticationMethodModel.None)
- underTest.start()
- assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
- // Swipe to Gone, more than halfway
- transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = Scenes.Lockscreen,
- toScene = Scenes.Gone,
- currentScene = flowOf(Scenes.Gone),
- progress = flowOf(0.51f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(true),
- )
- runCurrent()
- // Lift finger
- transitionStateFlow.value =
- ObservableTransitionState.Transition(
- fromScene = Scenes.Lockscreen,
- toScene = Scenes.Gone,
- currentScene = flowOf(Scenes.Gone),
- progress = flowOf(0.51f),
- isInitiatedByUserInput = true,
- isUserInputOngoing = flowOf(false),
- )
- runCurrent()
-
- assertThat(currentScene).isEqualTo(Scenes.Gone)
- }
-
- @Test
fun switchToGone_extendUnlock() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.currentScene)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
index 4d71dc45001d..4871564a5376 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/data/model/DisplayContentScenarios.kt
@@ -18,6 +18,8 @@ package com.android.systemui.screenshot.data.model
import android.content.ComponentName
import android.graphics.Rect
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
+import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_MAXIMIZED
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREE_FORM
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FULL_SCREEN
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.PIP
@@ -153,11 +155,23 @@ object DisplayContentScenarios {
fun freeFormApps(
vararg tasks: TaskSpec,
focusedTaskId: Int,
+ maximizedTaskId: Int = -1,
shadeExpanded: Boolean = false,
): DisplayContentModel {
val freeFormTasks =
tasks
- .map { freeForm(it) }
+ .map {
+ freeForm(
+ task = it,
+ bounds =
+ if (it.taskId == maximizedTaskId) {
+ FREEFORM_MAXIMIZED
+ } else {
+ FREE_FORM
+ },
+ maxBounds = FREEFORM_FULL_SCREEN,
+ )
+ }
// Root tasks are ordered top-down in List<RootTaskInfo>.
// Sort 'focusedTaskId' last (Boolean natural ordering: [false, true])
.sortedBy { it.childTaskIds[0] != focusedTaskId }
@@ -180,9 +194,9 @@ object DisplayContentScenarios {
val PIP = Rect(440, 1458, 1038, 1794)
val SPLIT_TOP = Rect(0, 0, 1080, 1187)
val SPLIT_BOTTOM = Rect(0, 1213, 1080, 2400)
- val FREE_FORM = Rect(119, 332, 1000, 1367)
// "Tablet" size
+ val FREE_FORM = Rect(119, 332, 1000, 1367)
val FREEFORM_FULL_SCREEN = Rect(0, 0, 2560, 1600)
val FREEFORM_MAXIMIZED = Rect(0, 48, 2560, 1480)
val FREEFORM_SPLIT_LEFT = Rect(0, 0, 1270, 1600)
@@ -301,11 +315,12 @@ object DisplayContentScenarios {
}
/** An activity in FreeForm mode */
- fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM) =
+ fun freeForm(task: TaskSpec, bounds: Rect = FREE_FORM, maxBounds: Rect = bounds) =
newRootTaskInfo(
taskId = task.taskId,
userId = task.userId,
bounds = bounds,
+ maxBounds = maxBounds,
windowingMode = WindowingMode.Freeform,
topActivity = ComponentName.unflattenFromString(task.name),
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
index cedf0c8a2c06..4f6871ebbbf4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/NewRootTaskInfo.kt
@@ -84,6 +84,7 @@ fun newRootTaskInfo(
activityType: ActivityType = Standard,
windowingMode: WindowingMode = FullScreen,
bounds: Rect = Rect(),
+ maxBounds: Rect = bounds,
topActivity: ComponentName? = null,
topActivityType: ActivityType = Standard,
numActivities: Int? = null,
@@ -94,6 +95,7 @@ fun newRootTaskInfo(
setWindowingMode(windowingMode.toInt())
setActivityType(activityType.toInt())
setBounds(bounds)
+ setMaxBounds(maxBounds)
}
this.bounds = bounds
this.displayId = displayId
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
index b7f565df4a3c..c884b9a9ac54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/PrivateProfilePolicyTest.kt
@@ -90,9 +90,11 @@ class PrivateProfilePolicyTest {
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ component =
+ ComponentName.unflattenFromString(YOUTUBE)
+ ?: error("Invalid component name"),
owner = UserHandle.of(PRIVATE),
),
)
@@ -142,7 +144,7 @@ class PrivateProfilePolicyTest {
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE),
owner = UserHandle.of(PRIVATE),
@@ -167,7 +169,7 @@ class PrivateProfilePolicyTest {
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(PRIVATE),
@@ -188,7 +190,7 @@ class PrivateProfilePolicyTest {
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE_PIP),
owner = UserHandle.of(PRIVATE),
@@ -212,7 +214,7 @@ class PrivateProfilePolicyTest {
Matched(
PrivateProfilePolicy.NAME,
PrivateProfilePolicy.PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(displayId = 0),
component = ComponentName.unflattenFromString(YOUTUBE_PIP),
owner = UserHandle.of(PRIVATE),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
index 28eb9fc1364b..948c24ec8aa2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/ScreenshotPolicyTest.kt
@@ -17,11 +17,11 @@
package com.android.systemui.screenshot.policy
import android.content.ComponentName
+import android.graphics.Rect
import android.os.UserHandle
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES
-import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.LAUNCHER
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.MESSAGES
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.YOUTUBE
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.Bounds.FREEFORM_FULL_SCREEN
@@ -32,10 +32,10 @@ import com.android.systemui.screenshot.data.model.DisplayContentScenarios.freeFo
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.pictureInPictureApp
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.singleFullScreen
import com.android.systemui.screenshot.data.model.DisplayContentScenarios.splitScreenApps
+import com.android.systemui.screenshot.data.model.allTasks
import com.android.systemui.screenshot.data.repository.profileTypeRepository
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
import com.android.systemui.screenshot.policy.TestUserIds.PERSONAL
import com.android.systemui.screenshot.policy.TestUserIds.PRIVATE
import com.android.systemui.screenshot.policy.TestUserIds.WORK
@@ -50,69 +50,81 @@ class ScreenshotPolicyTest {
private val defaultComponent = ComponentName("default", "default")
private val defaultOwner = UserHandle.SYSTEM
+ private val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
@Test
fun fullScreen_work() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+ val displayContent = singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK))
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
- val result =
- policy.apply(
- singleFullScreen(TaskSpec(taskId = 1002, name = FILES, userId = WORK)),
- defaultComponent,
- defaultOwner,
- )
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
- component = ComponentName.unflattenFromString(FILES),
- owner = UserHandle.of(WORK),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(expectedFocusedTask.userId),
)
)
}
@Test
fun fullScreen_private() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
+ val displayContent =
+ singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE))
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
- val result =
- policy.apply(
- singleFullScreen(TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE)),
- defaultComponent,
- defaultOwner,
- )
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
- owner = UserHandle.of(PRIVATE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(expectedFocusedTask.userId),
)
)
}
@Test
fun splitScreen_workAndPersonal() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PERSONAL),
)
)
@@ -120,24 +132,28 @@ class ScreenshotPolicyTest {
@Test
fun splitScreen_personalAndPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = PERSONAL),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
@@ -145,24 +161,28 @@ class ScreenshotPolicyTest {
@Test
fun splitScreen_workAndPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
@@ -170,32 +190,31 @@ class ScreenshotPolicyTest {
@Test
fun splitScreen_twoWorkTasks() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- splitScreenApps(
- parentTaskId = 1,
- parentBounds = FREEFORM_FULL_SCREEN,
- orientation = VERTICAL,
- first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ splitScreenApps(
+ parentTaskId = 1,
+ parentBounds = FREEFORM_FULL_SCREEN,
+ orientation = VERTICAL,
+ first = TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ second = TaskSpec(taskId = 1003, name = YOUTUBE, userId = WORK),
+ focusedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
- type =
- RootTask(
- parentTaskId = 1,
- taskBounds = FREEFORM_FULL_SCREEN,
- childTaskIds = listOf(1002, 1003),
+ type = IsolatedTask(taskBounds = FREEFORM_FULL_SCREEN, taskId = 1),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
),
- component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
)
)
@@ -203,99 +222,112 @@ class ScreenshotPolicyTest {
@Test
fun freeform_floatingWindows() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1003,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1003,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PERSONAL),
)
)
}
@Test
- fun freeform_floatingWindows_maximized() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
- focusedTaskId = 1003,
- ),
- defaultComponent,
- defaultOwner,
+ fun freeform_floatingWindows_work_maximized() = runTest {
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PERSONAL),
+ focusedTaskId = 1002,
+ maximizedTaskId = 1002,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
- type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
- owner = UserHandle.of(PERSONAL),
+ type = IsolatedTask(taskId = 1002, taskBounds = expectedFocusedTask.bounds),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(WORK),
)
)
}
@Test
fun freeform_floatingWindows_withPrivate() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
- TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
- focusedTaskId = 1004,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ freeFormApps(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ TaskSpec(taskId = 1003, name = YOUTUBE, userId = PRIVATE),
+ TaskSpec(taskId = 1004, name = MESSAGES, userId = PERSONAL),
+ focusedTaskId = 1004,
)
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1004 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(YOUTUBE),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(PRIVATE),
)
)
}
@Test
- fun freeform_floating_workOnly() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- freeFormApps(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- focusedTaskId = 1002,
- ),
- defaultComponent,
- defaultOwner,
- )
+ fun freeform_floating_work() = runTest {
+ val displayContent =
+ freeFormApps(TaskSpec(taskId = 1002, name = FILES, userId = WORK), focusedTaskId = 1002)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = ComponentName.unflattenFromString(LAUNCHER),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = defaultOwner,
)
)
@@ -303,23 +335,27 @@ class ScreenshotPolicyTest {
@Test
fun fullScreen_shadeExpanded() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- singleFullScreen(
- TaskSpec(taskId = 1002, name = FILES, userId = WORK),
- shadeExpanded = true,
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ singleFullScreen(
+ TaskSpec(taskId = 1002, name = FILES, userId = WORK),
+ shadeExpanded = true,
)
+ val expectedFocusedTask =
+ displayContent.rootTasks.first().childTasksTopDown().single { it.id == 1002 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = FullScreen(displayId = 0),
- component = defaultComponent,
+ contentTask =
+ TaskReference(
+ taskId = -1,
+ component = defaultComponent,
+ owner = defaultOwner,
+ bounds = Rect(),
+ ),
owner = defaultOwner,
)
)
@@ -327,25 +363,55 @@ class ScreenshotPolicyTest {
@Test
fun fullScreen_with_PictureInPicture() = runTest {
- val policy = ScreenshotPolicy(kosmos.profileTypeRepository)
-
- val result =
- policy.apply(
- pictureInPictureApp(
- pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
- fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
- ),
- defaultComponent,
- defaultOwner,
+ val displayContent =
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PERSONAL),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = WORK),
)
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
assertThat(result)
.isEqualTo(
CaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
- component = ComponentName.unflattenFromString(FILES),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
owner = UserHandle.of(WORK),
)
)
}
+
+ // TODO: PiP tasks should affect ownership (e.g. Private)
+ @Test
+ fun fullScreen_with_PictureInPicture_private() = runTest {
+ val displayContent =
+ pictureInPictureApp(
+ pip = TaskSpec(taskId = 1002, name = YOUTUBE, userId = PRIVATE),
+ fullScreen = TaskSpec(taskId = 1003, name = FILES, userId = PERSONAL),
+ )
+ val expectedFocusedTask = displayContent.allTasks().single { it.id == 1003 }
+
+ val result = policy.apply(displayContent, defaultComponent, defaultOwner)
+ assertThat(result)
+ .isEqualTo(
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ contentTask =
+ TaskReference(
+ taskId = expectedFocusedTask.id,
+ component = expectedFocusedTask.componentName,
+ owner = UserHandle.of(expectedFocusedTask.userId),
+ bounds = expectedFocusedTask.bounds,
+ ),
+ owner = UserHandle.of(PRIVATE),
+ )
+ )
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
index 30a786c291cb..c1477fe52f0e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/policy/WorkProfilePolicyTest.kt
@@ -135,7 +135,7 @@ class WorkProfilePolicyTest {
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -162,7 +162,7 @@ class WorkProfilePolicyTest {
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1002, taskBounds = FULL_SCREEN.splitTop(20)),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -200,7 +200,7 @@ class WorkProfilePolicyTest {
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FULL_SCREEN),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
@@ -226,7 +226,7 @@ class WorkProfilePolicyTest {
PolicyResult.Matched(
policy = WorkProfilePolicy.NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = 1003, taskBounds = FREE_FORM),
component = ComponentName.unflattenFromString(FILES),
owner = UserHandle.of(WORK),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 2e759a363e20..443595dbdf77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -17,7 +17,6 @@
package com.android.systemui.shade;
import static kotlinx.coroutines.flow.StateFlowKt.MutableStateFlow;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
@@ -63,8 +62,6 @@ import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.QsFrameTranslateController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.DozeParameters;
@@ -159,8 +156,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
protected SysuiStatusBarStateController mStatusBarStateController;
protected ShadeInteractor mShadeInteractor;
- protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
-
protected Handler mMainHandler;
protected LockscreenShadeTransitionController.Callback mLockscreenShadeTransitionCallback;
@@ -204,11 +199,6 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
),
mKosmos.getShadeModeInteractor());
- mActiveNotificationsInteractor = new ActiveNotificationsInteractor(
- new ActiveNotificationListRepository(),
- StandardTestDispatcher(/* scheduler = */ null, /* name = */ null)
- );
-
KeyguardStatusView keyguardStatusView = new KeyguardStatusView(mContext);
keyguardStatusView.setId(R.id.keyguard_status_view);
@@ -277,7 +267,7 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase {
mock(DeviceEntryFaceAuthInteractor.class),
mShadeRepository,
mShadeInteractor,
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
new JavaAdapter(mTestScope.getBackgroundScope()),
mCastController,
splitShadeStateController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 943fb62003cb..0f476d0a0455 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -35,8 +35,7 @@ import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInte
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -50,7 +49,6 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
-import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -65,8 +63,6 @@ import org.mockito.MockitoAnnotations
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
- private val testDispatcher = StandardTestDispatcher()
- private val activeNotificationsRepository = ActiveNotificationListRepository()
private val kosmos = Kosmos()
private val testScope = kosmos.testScope
@@ -95,7 +91,7 @@ class ShadeControllerImplTest : SysuiTestCase() {
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
- ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher),
+ kosmos.activeNotificationsInteractor,
kosmos::sceneInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
index ce9b3beeec9e..7842d75db6c4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -31,6 +31,7 @@ import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
@@ -52,6 +53,10 @@ import org.mockito.kotlin.never
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class CommunalSmartspaceControllerTest : SysuiTestCase() {
+ @Mock private lateinit var userTracker: UserTracker
+
+ @Mock private lateinit var userContextPrimary: Context
+
@Mock private lateinit var smartspaceManager: SmartspaceManager
@Mock private lateinit var execution: Execution
@@ -113,15 +118,18 @@ class CommunalSmartspaceControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
controller =
CommunalSmartspaceController(
- context,
- smartspaceManager,
+ userTracker,
execution,
uiExecutor,
precondition,
Optional.of(targetFilter),
- Optional.of(plugin)
+ Optional.of(plugin),
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index e774aed2045b..c83c82dc0914 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.BcSmartspaceConfigPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.mockito.any
@@ -46,66 +47,51 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.anyInt
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
import org.mockito.MockitoAnnotations
-import org.mockito.Spy
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class DreamSmartspaceControllerTest : SysuiTestCase() {
- @Mock
- private lateinit var smartspaceManager: SmartspaceManager
+ @Mock private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var execution: Execution
+ @Mock private lateinit var userContextPrimary: Context
- @Mock
- private lateinit var uiExecutor: Executor
+ @Mock private lateinit var smartspaceManager: SmartspaceManager
- @Mock
- private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
+ @Mock private lateinit var execution: Execution
- @Mock
- private lateinit var viewComponent: SmartspaceViewComponent
+ @Mock private lateinit var uiExecutor: Executor
- @Mock
- private lateinit var weatherViewComponent: SmartspaceViewComponent
+ @Mock private lateinit var viewComponentFactory: SmartspaceViewComponent.Factory
- private val weatherSmartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var viewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var targetFilter: SmartspaceTargetFilter
+ @Mock private lateinit var weatherViewComponent: SmartspaceViewComponent
- @Mock
- private lateinit var plugin: BcSmartspaceDataPlugin
+ private val weatherSmartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
- @Mock
- private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+ @Mock private lateinit var targetFilter: SmartspaceTargetFilter
- @Mock
- private lateinit var precondition: SmartspacePrecondition
+ @Mock private lateinit var plugin: BcSmartspaceDataPlugin
- private val smartspaceView: SmartspaceView by lazy {
- Mockito.spy(TestView(context))
- }
+ @Mock private lateinit var weatherPlugin: BcSmartspaceDataPlugin
+
+ @Mock private lateinit var precondition: SmartspacePrecondition
- @Mock
- private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+ private val smartspaceView: SmartspaceView by lazy { Mockito.spy(TestView(context)) }
- @Mock
- private lateinit var session: SmartspaceSession
+ @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+ @Mock private lateinit var session: SmartspaceSession
private lateinit var controller: DreamSmartspaceController
// TODO(b/272811280): Remove usage of real view
- private val fakeParent by lazy {
- FrameLayout(context)
- }
+ private val fakeParent by lazy { FrameLayout(context) }
/**
* A class which implements SmartspaceView and extends View. This is mocked to provide the right
@@ -134,30 +120,44 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
override fun setMediaTarget(target: SmartspaceTarget?) {}
- override fun getSelectedPage(): Int { return 0; }
+ override fun getSelectedPage(): Int {
+ return 0
+ }
- override fun getCurrentCardTopPadding(): Int { return 0; }
+ override fun getCurrentCardTopPadding(): Int {
+ return 0
+ }
}
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(viewComponentFactory.create(any(), eq(plugin), any(), eq(null)))
- .thenReturn(viewComponent)
+ .thenReturn(viewComponent)
`when`(viewComponent.getView()).thenReturn(smartspaceView)
`when`(viewComponentFactory.create(any(), eq(weatherPlugin), any(), any()))
.thenReturn(weatherViewComponent)
`when`(weatherViewComponent.getView()).thenReturn(weatherSmartspaceView)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
- controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
- viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin),
- Optional.of(weatherPlugin))
+ `when`(userTracker.userContext).thenReturn(userContextPrimary)
+ `when`(userContextPrimary.getSystemService(SmartspaceManager::class.java))
+ .thenReturn(smartspaceManager)
+
+ controller =
+ DreamSmartspaceController(
+ userTracker,
+ execution,
+ uiExecutor,
+ viewComponentFactory,
+ precondition,
+ Optional.of(targetFilter),
+ Optional.of(plugin),
+ Optional.of(weatherPlugin),
+ )
}
- /**
- * Ensures smartspace session begins on a listener only flow.
- */
+ /** Ensures smartspace session begins on a listener only flow. */
@Test
fun testConnectOnListen() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -165,18 +165,18 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(smartspaceManager).createSmartspaceSession(any())
- var targetListener = withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
- verify(session).addOnTargetsAvailableListener(any(), capture())
- }
+ var targetListener =
+ withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+ verify(session).addOnTargetsAvailableListener(any(), capture())
+ }
`when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
var target = Mockito.mock(SmartspaceTarget::class.java)
targetListener.onTargetsAvailable(listOf(target))
- var targets = withArgCaptor<List<SmartspaceTarget>> {
- verify(plugin).onTargetsAvailable(capture())
- }
+ var targets =
+ withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
assertThat(targets.contains(target)).isTrue()
@@ -185,17 +185,16 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(session).close()
}
- /**
- * Ensures session begins when a view is attached.
- */
+ /** Ensures session begins when a view is attached. */
@Test
fun testConnectOnViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
- val stateChangeListener = withArgCaptor<View.OnAttachStateChangeListener> {
- verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
- }
+ val stateChangeListener =
+ withArgCaptor<View.OnAttachStateChangeListener> {
+ verify(viewComponentFactory).create(any(), eq(plugin), capture(), eq(null))
+ }
val mockView = Mockito.mock(TestView::class.java)
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -209,9 +208,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(session).close()
}
- /**
- * Ensures session is created when weather smartspace view is created and attached.
- */
+ /** Ensures session is created when weather smartspace view is created and attached. */
@Test
fun testConnectOnWeatherViewCreate() {
`when`(precondition.conditionsMet()).thenReturn(true)
@@ -223,8 +220,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
// Then weather view is created with custom view and the default weatherPlugin.getView
// should not be called
- verify(viewComponentFactory).create(eq(fakeParent), eq(weatherPlugin), any(),
- eq(customView))
+ verify(viewComponentFactory)
+ .create(eq(fakeParent), eq(weatherPlugin), any(), eq(customView))
verify(weatherPlugin, Mockito.never()).getView(fakeParent)
// And then session is created
@@ -234,9 +231,7 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(weatherSmartspaceView).setDozeAmount(0f)
}
- /**
- * Ensures weather plugin registers target listener when it is added from the controller.
- */
+ /** Ensures weather plugin registers target listener when it is added from the controller. */
@Test
fun testAddListenerInController_registersListenerForWeatherPlugin() {
val customView = Mockito.mock(TestView::class.java)
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 6e190965d08b..32f4164d509f 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
@@ -66,18 +66,34 @@ class NotifChipsViewModelTest : SysuiTestCase() {
testScope.runTest {
val latest by collectLastValue(underTest.chips)
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = null,
+ isPromoted = true,
+ )
+ )
+ )
assertThat(latest).isEmpty()
}
@Test
- fun chips_oneNotif_statusBarIconViewMatches() =
+ fun chips_onePromotedNotif_statusBarIconViewMatches() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
assertThat(latest).hasSize(1)
val chip = latest!![0]
@@ -86,7 +102,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
}
@Test
- fun chips_twoNotifs_twoChips() =
+ fun chips_onlyForPromotedNotifs() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -94,8 +110,21 @@ class NotifChipsViewModelTest : SysuiTestCase() {
val secondIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon),
+ activeNotificationModel(
+ key = "notif1",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif2",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "notif3",
+ statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = false,
+ ),
)
)
@@ -118,6 +147,7 @@ class NotifChipsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "clickTest",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
)
)
)
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 b12d7c57e1fd..25d5ce50e03f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt
@@ -293,19 +293,27 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() =
+ fun chips_singlePromotedNotif_primaryIsNotifSecondaryIsHidden() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
val icon = mock<StatusBarIconView>()
- setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon)))
+ setNotifs(
+ listOf(
+ activeNotificationModel(
+ key = "notif",
+ statusBarChipIcon = icon,
+ isPromoted = true,
+ )
+ )
+ )
assertIsNotifChip(latest!!.primary, icon)
assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java)
}
@Test
- fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() =
+ fun chips_twoPromotedNotifs_primaryAndSecondaryAreNotifsInOrder() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -313,8 +321,16 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val secondIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
)
)
@@ -323,7 +339,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun chips_threeNotifChips_topTwoShown() =
+ fun chips_threePromotedNotifs_topTwoShown() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -332,9 +348,21 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val thirdIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
- activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon),
- activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "secondNotif",
+ statusBarChipIcon = secondIcon,
+ isPromoted = true,
+ ),
+ activeNotificationModel(
+ key = "thirdNotif",
+ statusBarChipIcon = thirdIcon,
+ isPromoted = true,
+ ),
)
)
@@ -343,7 +371,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() =
+ fun chips_callAndPromotedNotifs_primaryIsCallSecondaryIsNotif() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -351,10 +379,15 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
val firstIcon = mock<StatusBarIconView>()
setNotifs(
listOf(
- activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon),
+ activeNotificationModel(
+ key = "firstNotif",
+ statusBarChipIcon = firstIcon,
+ isPromoted = true,
+ ),
activeNotificationModel(
key = "secondNotif",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
),
)
)
@@ -364,7 +397,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
}
@Test
- fun chips_screenRecordAndCallAndNotifs_notifsNotShown() =
+ fun chips_screenRecordAndCallAndPromotedNotifs_notifsNotShown() =
testScope.runTest {
val latest by collectLastValue(underTest.chips)
@@ -375,6 +408,7 @@ class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() {
activeNotificationModel(
key = "notif",
statusBarChipIcon = mock<StatusBarIconView>(),
+ isPromoted = true,
)
)
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
index 20a19a9b1399..938da88a8819 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt
@@ -26,11 +26,14 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.fragments.FragmentHostManager
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import com.android.systemui.statusbar.pipeline.shared.ui.composable.StatusBarRootFactory
import com.android.systemui.statusbar.window.StatusBarWindowController
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.test.Test
import org.junit.Assert.assertThrows
@@ -45,12 +48,14 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
class StatusBarInitializerTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
private val windowController = mock(StatusBarWindowController::class.java)
private val windowControllerStore = mock(StatusBarWindowControllerStore::class.java)
private val transaction = mock(FragmentTransaction::class.java)
private val fragmentManager = mock(FragmentManager::class.java)
private val fragmentHostManager = mock(FragmentHostManager::class.java)
private val backgroundView = mock(ViewGroup::class.java)
+ private val statusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository
@Before
fun setup() {
@@ -72,6 +77,7 @@ class StatusBarInitializerTest : SysuiTestCase() {
statusBarRootFactory = mock(StatusBarRootFactory::class.java),
componentFactory = mock(HomeStatusBarComponent.Factory::class.java),
creationListeners = setOf(),
+ statusBarModePerDisplayRepository = statusBarModePerDisplayRepository,
)
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
new file mode 100644
index 000000000000..18eef33813f6
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreImplTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.never
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LightBarControllerStoreImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+
+ private val underTest = kosmos.lightBarControllerStoreImpl
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun beforeDisplayRemoved_doesNotStopInstances() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance, never()).stop()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
new file mode 100644
index 000000000000..a9920ec5e29b
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/data/repository/MultiDisplayStatusBarModeRepositoryStoreTest.kt
@@ -0,0 +1,70 @@
+/*
+ * 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.statusbar.data.repository
+
+import android.platform.test.annotations.EnableFlags
+import android.view.Display.DEFAULT_DISPLAY
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
+import com.android.systemui.testKosmos
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME)
+class MultiDisplayStatusBarModeRepositoryStoreTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+ private val testScope = kosmos.testScope
+ private val fakeDisplayRepository = kosmos.displayRepository
+ private val underTest by lazy { kosmos.multiDisplayStatusBarModeRepositoryStore }
+
+ @Before
+ fun start() {
+ underTest.start()
+ }
+
+ @Before fun addDisplays() = runBlocking { fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY) }
+
+ @Test
+ fun forDisplay_startsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).start()
+ }
+
+ @Test
+ fun displayRemoved_stopsInstance() =
+ testScope.runTest {
+ val instance = underTest.forDisplay(DEFAULT_DISPLAY)
+
+ fakeDisplayRepository.removeDisplay(DEFAULT_DISPLAY)
+
+ verify(instance).stop()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
index 9f40f60db45f..99bda856818e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractorTest.kt
@@ -18,11 +18,14 @@
package com.android.systemui.statusbar.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
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
import com.android.systemui.statusbar.notification.collection.render.NotifStats
import com.android.systemui.statusbar.notification.data.model.activeNotificationModel
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
@@ -44,7 +47,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val activeNotificationListRepository = kosmos.activeNotificationListRepository
- private val underTest = kosmos.activeNotificationsInteractor
+ private val underTest by lazy { kosmos.activeNotificationsInteractor }
@Test
fun testAllNotificationsCount() =
@@ -65,14 +68,8 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
val normalNotifs =
listOf(
- activeNotificationModel(
- key = "notif1",
- callType = CallType.None,
- ),
- activeNotificationModel(
- key = "notif2",
- callType = CallType.None,
- )
+ activeNotificationModel(key = "notif1", callType = CallType.None),
+ activeNotificationModel(key = "notif2", callType = CallType.None),
)
activeNotificationListRepository.activeNotifications.value =
@@ -129,10 +126,7 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
val latest by collectLastValue(underTest.ongoingCallNotification)
val ongoingNotif =
- activeNotificationModel(
- key = "ongoingNotif",
- callType = CallType.Ongoing,
- )
+ activeNotificationModel(key = "ongoingNotif", callType = CallType.Ongoing)
activeNotificationListRepository.activeNotifications.value =
ActiveNotificationsStore.Builder()
@@ -170,6 +164,62 @@ class ActiveNotificationsInteractorTest : SysuiTestCase() {
}
@Test
+ @DisableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_flagOff_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+ val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(promoted1) }
+ .apply { addIndividualNotif(notPromoted2) }
+ .build()
+
+ assertThat(latest!!).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_nonePromoted_empty() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { activeNotificationModel(key = "notif1", isPromoted = false) }
+ .apply { activeNotificationModel(key = "notif2", isPromoted = false) }
+ .apply { activeNotificationModel(key = "notif3", isPromoted = false) }
+ .build()
+
+ assertThat(latest!!).isEmpty()
+ }
+
+ @Test
+ @EnableFlags(StatusBarNotifChips.FLAG_NAME)
+ fun promotedOngoingNotifications_somePromoted_hasOnlyPromoted() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.promotedOngoingNotifications)
+
+ val promoted1 = activeNotificationModel(key = "notif1", isPromoted = true)
+ val notPromoted2 = activeNotificationModel(key = "notif2", isPromoted = false)
+ val notPromoted3 = activeNotificationModel(key = "notif3", isPromoted = false)
+ val promoted4 = activeNotificationModel(key = "notif4", isPromoted = true)
+
+ activeNotificationListRepository.activeNotifications.value =
+ ActiveNotificationsStore.Builder()
+ .apply { addIndividualNotif(promoted1) }
+ .apply { addIndividualNotif(notPromoted2) }
+ .apply { addIndividualNotif(notPromoted3) }
+ .apply { addIndividualNotif(promoted4) }
+ .build()
+
+ assertThat(latest!!).containsExactly(promoted1, promoted4)
+ }
+
+ @Test
fun areAnyNotificationsPresent_isTrue() =
testScope.runTest {
val areAnyNotificationsPresent by collectLastValue(underTest.areAnyNotificationsPresent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
index 572a0c1b1869..183f9016a23b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationsListInteractorTest.kt
@@ -16,22 +16,25 @@
package com.android.systemui.statusbar.notification.domain.interactor
import android.app.Notification
-import android.os.Bundle
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.EnableFlags
import android.service.notification.StatusBarNotification
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.RankingBuilder
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
import com.android.systemui.statusbar.notification.shared.byKey
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,16 +42,16 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
class RenderNotificationsListInteractorTest : SysuiTestCase() {
- private val backgroundDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(backgroundDispatcher)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
- private val notifsRepository = ActiveNotificationListRepository()
- private val notifsInteractor =
- ActiveNotificationsInteractor(notifsRepository, backgroundDispatcher)
+ private val notifsRepository = kosmos.activeNotificationListRepository
+ private val notifsInteractor = kosmos.activeNotificationsInteractor
private val underTest =
RenderNotificationListInteractor(
notifsRepository,
sectionStyleProvider = mock(),
+ promotedNotificationsProvider = kosmos.promotedNotificationsProvider,
)
@Test
@@ -85,12 +88,7 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
assertThat(ranks)
.containsExactlyEntriesIn(
- mapOf(
- "single" to 0,
- "summary" to 1,
- "child0" to 2,
- "child1" to 3,
- )
+ mapOf("single" to 0, "summary" to 1, "child0" to 2, "child1" to 3)
)
}
@@ -126,6 +124,53 @@ class RenderNotificationsListInteractorTest : SysuiTestCase() {
assertThat(actual).containsAtLeastEntriesIn(expected)
}
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun setRenderList_setsPromotionStatus() =
+ testScope.runTest {
+ val actual by collectLastValue(notifsInteractor.topLevelRepresentativeNotifications)
+
+ val notPromoted1 = mockNotificationEntry("key1", flag = null)
+ val promoted2 = mockNotificationEntry("key2", flag = FLAG_PROMOTED_ONGOING)
+
+ underTest.setRenderedList(listOf(notPromoted1, promoted2))
+
+ assertThat(actual!!.size).isEqualTo(2)
+
+ val first = actual!![0]
+ assertThat(first.key).isEqualTo("key1")
+ assertThat(first.isPromoted).isFalse()
+
+ val second = actual!![1]
+ assertThat(second.key).isEqualTo("key2")
+ assertThat(second.isPromoted).isTrue()
+ }
+
+ private fun mockNotificationEntry(
+ key: String,
+ rank: Int = 0,
+ flag: Int? = null,
+ ): NotificationEntry {
+ val nBuilder = Notification.Builder(context, "a")
+ if (flag != null) {
+ nBuilder.setFlag(flag, true)
+ }
+ val notification = nBuilder.build()
+
+ val mockSbn =
+ mock<StatusBarNotification>() {
+ whenever(this.notification).thenReturn(notification)
+ whenever(packageName).thenReturn("com.android")
+ }
+ return mock<NotificationEntry> {
+ whenever(this.key).thenReturn(key)
+ whenever(this.icons).thenReturn(mock())
+ whenever(this.representativeEntry).thenReturn(this)
+ whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
+ whenever(this.sbn).thenReturn(mockSbn)
+ }
+ }
}
private fun mockGroupEntry(
@@ -139,19 +184,3 @@ private fun mockGroupEntry(
whenever(this.children).thenReturn(children)
}
}
-
-private fun mockNotificationEntry(key: String, rank: Int = 0): NotificationEntry {
- val mockNotification = mock<Notification> { this.extras = Bundle() }
- val mockSbn =
- mock<StatusBarNotification>() {
- whenever(notification).thenReturn(mockNotification)
- whenever(packageName).thenReturn("com.android")
- }
- return mock<NotificationEntry> {
- whenever(this.key).thenReturn(key)
- whenever(this.icons).thenReturn(mock())
- whenever(this.representativeEntry).thenReturn(this)
- whenever(this.ranking).thenReturn(RankingBuilder().setRank(rank).build())
- whenever(this.sbn).thenReturn(mockSbn)
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
new file mode 100644
index 000000000000..a9dbe63e8f07
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderTest.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.statusbar.notification.promoted
+
+import android.app.Notification
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+
+@SmallTest
+class PromotedNotificationsProviderTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+
+ private val underTest = kosmos.promotedNotificationsProvider
+
+ @Test
+ @DisableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOff_false() {
+ val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+ assertThat(underTest.shouldPromote(entry)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOn_notifDoesNotHaveFlag_false() {
+ val entry = createNotification(flag = null)
+
+ assertThat(underTest.shouldPromote(entry)).isFalse()
+ }
+
+ @Test
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
+ fun shouldPromote_uiFlagOn_notifHasFlag_true() {
+ val entry = createNotification(FLAG_PROMOTED_ONGOING)
+
+ assertThat(underTest.shouldPromote(entry)).isTrue()
+ }
+
+ private fun createNotification(flag: Int? = null): NotificationEntry {
+ val n = Notification.Builder(context, "a")
+ if (flag != null) {
+ n.setFlag(flag, true)
+ }
+
+ return NotificationEntryBuilder().setNotification(n.build()).build()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
index e9d88ccb0cbc..122cfdd1a045 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationLoggerViewModelTest.kt
@@ -16,16 +16,22 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
-import androidx.test.ext.junit.runners.AndroidJUnit4
+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.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.scene.data.repository.Idle
+import com.android.systemui.scene.data.repository.setSceneTransition
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
+import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.testKosmos
@@ -33,10 +39,12 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class NotificationLoggerViewModelTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class NotificationLoggerViewModelTest(flags: FlagsParameterization) : SysuiTestCase() {
private val kosmos = testKosmos()
@@ -46,9 +54,22 @@ class NotificationLoggerViewModelTest : SysuiTestCase() {
private val powerInteractor = kosmos.powerInteractor
private val windowRootViewVisibilityRepository = kosmos.windowRootViewVisibilityRepository
- private val underTest = kosmos.notificationListLoggerViewModel
+ private val underTest by lazy { kosmos.notificationListLoggerViewModel }
+
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf().andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags)
+ }
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsInteractive_true() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -60,6 +81,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceIsAsleepAndShadeIsInteractive_false() =
testScope.runTest {
powerInteractor.setAsleepForTest()
@@ -71,6 +93,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsNotInteractive_false() =
testScope.runTest {
powerInteractor.setAwakeForTest()
@@ -82,6 +105,54 @@ class NotificationLoggerViewModelTest : SysuiTestCase() {
}
@Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceAwakeAndShadeIsDisplayed_true() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Shade))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceAwakeAndLockScreenIsDisplayed_true() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isTrue()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceIsAsleepOnLockscreen_false() =
+ testScope.runTest {
+ powerInteractor.setAsleepForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Lockscreen))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun isLockscreenOrShadeInteractive_deviceActiveAndShadeIsClosed() =
+ testScope.runTest {
+ powerInteractor.setAwakeForTest()
+ kosmos.setSceneTransition(Idle(Scenes.Gone))
+
+ val actual by collectLastValue(underTest.isLockscreenOrShadeInteractive)
+
+ assertThat(actual).isFalse()
+ }
+
+ @Test
fun activeNotifications_hasNotifications() =
testScope.runTest {
activeNotificationListRepository.setActiveNotifs(5)
@@ -135,6 +206,7 @@ class NotificationLoggerViewModelTest : SysuiTestCase() {
assertThat(isOnLockScreen).isTrue()
}
+
@Test
fun isOnLockScreen_false() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
deleted file mode 100644
index 157f8189276a..000000000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ /dev/null
@@ -1,845 +0,0 @@
-/*
- * Copyright (C) 2021 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.phone;
-
-
-import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
-import static android.app.StatusBarManager.DISABLE_SYSTEM_INFO;
-
-import static com.android.systemui.Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND;
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.platform.test.annotations.DisableFlags;
-import android.platform.test.annotations.EnableFlags;
-import android.provider.Settings;
-import android.testing.TestableLooper;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.CarrierTextController;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.keyguard.logging.KeyguardLogger;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.battery.BatteryMeterViewController;
-import com.android.systemui.flags.DisableSceneContainer;
-import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.kosmos.KosmosJavaAdapter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.res.R;
-import com.android.systemui.shade.ShadeViewStateProvider;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore;
-import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
-import com.android.systemui.statusbar.phone.ui.StatusBarIconController;
-import com.android.systemui.statusbar.phone.ui.TintedIconManager;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.UserInfoController;
-import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-@TestableLooper.RunWithLooper
-public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
- @Mock
- private CarrierTextController mCarrierTextController;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private SystemStatusAnimationScheduler mAnimationScheduler;
- @Mock
- private BatteryController mBatteryController;
- @Mock
- private UserInfoController mUserInfoController;
- @Mock
- private StatusBarIconController mStatusBarIconController;
- @Mock
- private TintedIconManager.Factory mIconManagerFactory;
- @Mock
- private TintedIconManager mIconManager;
- @Mock
- private BatteryMeterViewController mBatteryMeterViewController;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
- @Mock
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock
- private BiometricUnlockController mBiometricUnlockController;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private StatusBarContentInsetsProvider mStatusBarContentInsetsProvider;
- @Mock
- private StatusBarContentInsetsProviderStore mStatusBarContentInsetsProviderStore;
- @Mock
- private UserManager mUserManager;
- @Mock
- private StatusBarUserChipViewModel mStatusBarUserChipViewModel;
- @Captor
- private ArgumentCaptor<ConfigurationListener> mConfigurationListenerCaptor;
- @Captor
- private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardCallbackCaptor;
- @Mock private SecureSettings mSecureSettings;
- @Mock private CommandQueue mCommandQueue;
- @Mock private KeyguardLogger mLogger;
- @Mock private StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
-
- private TestShadeViewStateProvider mShadeViewStateProvider;
- private KeyguardStatusBarView mKeyguardStatusBarView;
- private KeyguardStatusBarViewController mController;
- private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
- private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock());
- private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
-
- @Before
- public void setup() throws Exception {
- mShadeViewStateProvider = new TestShadeViewStateProvider();
-
- MockitoAnnotations.initMocks(this);
- when(mStatusBarContentInsetsProviderStore.getDefaultDisplay())
- .thenReturn(mStatusBarContentInsetsProvider);
- when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
-
- allowTestableLooperAsMainThread();
- TestableLooper.get(this).runWithLooper(() -> {
- mKeyguardStatusBarView =
- spy((KeyguardStatusBarView) LayoutInflater.from(mContext)
- .inflate(R.layout.keyguard_status_bar, null));
- when(mKeyguardStatusBarView.getDisplay()).thenReturn(mContext.getDisplay());
- });
-
- mController = createController();
- }
-
- private KeyguardStatusBarViewController createController() {
- return new KeyguardStatusBarViewController(
- mKeyguardStatusBarView,
- mCarrierTextController,
- mConfigurationController,
- mAnimationScheduler,
- mBatteryController,
- mUserInfoController,
- mStatusBarIconController,
- mIconManagerFactory,
- mBatteryMeterViewController,
- mShadeViewStateProvider,
- mKeyguardStateController,
- mKeyguardBypassController,
- mKeyguardUpdateMonitor,
- mKosmos.getKeyguardStatusBarViewModel(),
- mBiometricUnlockController,
- mStatusBarStateController,
- mStatusBarContentInsetsProviderStore,
- mUserManager,
- mStatusBarUserChipViewModel,
- mSecureSettings,
- mCommandQueue,
- mFakeExecutor,
- mBackgroundExecutor,
- mLogger,
- mStatusOverlayHoverListenerFactory,
- mKosmos.getCommunalSceneInteractor()
- );
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
- mController.onViewAttached();
-
- runAllScheduled();
- verify(mConfigurationController).addCallback(any());
- verify(mAnimationScheduler).addCallback(any());
- verify(mUserInfoController).addCallback(any());
- verify(mCommandQueue).addCallback(any());
- verify(mStatusBarIconController).addIconGroup(any());
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
- mController.onViewAttached();
-
- verify(mConfigurationController).addCallback(any());
- verify(mAnimationScheduler).addCallback(any());
- verify(mUserInfoController).addCallback(any());
- verify(mCommandQueue).addCallback(any());
- verify(mStatusBarIconController).addIconGroup(any());
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- runAllScheduled();
- verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mConfigurationListenerCaptor.getValue().onConfigChanged(null);
-
- runAllScheduled();
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- verify(mConfigurationController).addCallback(mConfigurationListenerCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mConfigurationListenerCaptor.getValue().onConfigChanged(null);
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @EnableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- runAllScheduled();
- verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
-
- runAllScheduled();
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- @DisableFlags(FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
- public void
- onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
- mController.onViewAttached();
- verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardCallbackCaptor.capture());
- clearInvocations(mUserManager);
- clearInvocations(mKeyguardStatusBarView);
-
- mKeyguardCallbackCaptor.getValue().onKeyguardVisibilityChanged(true);
- verify(mUserManager).isUserSwitcherEnabled(anyBoolean());
- verify(mKeyguardStatusBarView).setUserSwitcherEnabled(anyBoolean());
- }
-
- @Test
- public void onViewDetached_callbacksUnregistered() {
- // Set everything up first.
- mController.onViewAttached();
-
- mController.onViewDetached();
-
- verify(mConfigurationController).removeCallback(any());
- verify(mAnimationScheduler).removeCallback(any());
- verify(mUserInfoController).removeCallback(any());
- verify(mCommandQueue).removeCallback(any());
- verify(mStatusBarIconController).removeIconGroup(any());
- }
-
- @Test
- @DisableSceneContainer
- public void onViewReAttached_flagOff_iconManagerNotReRegistered() {
- mController.onViewAttached();
- mController.onViewDetached();
- reset(mStatusBarIconController);
-
- mController.onViewAttached();
-
- verify(mStatusBarIconController, never()).addIconGroup(any());
- }
-
- @Test
- @EnableSceneContainer
- public void onViewReAttached_flagOn_iconManagerReRegistered() {
- mController.onViewAttached();
- mController.onViewDetached();
- reset(mStatusBarIconController);
-
- mController.onViewAttached();
-
- verify(mStatusBarIconController).addIconGroup(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_true_callbackAdded() {
- mController.setBatteryListening(true);
-
- verify(mBatteryController).addCallback(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_false_callbackRemoved() {
- // First set to true so that we know setting to false is a change in state.
- mController.setBatteryListening(true);
-
- mController.setBatteryListening(false);
-
- verify(mBatteryController).removeCallback(any());
- }
-
- @Test
- @DisableSceneContainer
- public void setBatteryListening_trueThenTrue_callbackAddedOnce() {
- mController.setBatteryListening(true);
- mController.setBatteryListening(true);
-
- verify(mBatteryController).addCallback(any());
- }
-
- @Test
- @EnableSceneContainer
- public void setBatteryListening_true_flagOn_callbackNotAdded() {
- mController.setBatteryListening(true);
-
- verify(mBatteryController, never()).addCallback(any());
- }
-
- @Test
- public void updateTopClipping_viewClippingUpdated() {
- int viewTop = 20;
- mKeyguardStatusBarView.setTop(viewTop);
- int notificationPanelTop = 30;
-
- mController.updateTopClipping(notificationPanelTop);
-
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(
- notificationPanelTop - viewTop);
- }
-
- @Test
- public void setNotTopClipping_viewClippingUpdatedToZero() {
- // Start out with some amount of top clipping.
- mController.updateTopClipping(50);
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isGreaterThan(0);
-
- mController.setNoTopClipping();
-
- assertThat(mKeyguardStatusBarView.getClipBounds().top).isEqualTo(0);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_alphaAndVisibilityGiven_viewUpdated() {
- // Verify the initial values so we know the method triggers changes.
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(1f);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- float newAlpha = 0.5f;
- int newVisibility = View.INVISIBLE;
- mController.updateViewState(newAlpha, newVisibility);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(newAlpha);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(newVisibility);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
- mController.onViewAttached();
- setDisableSystemIcons(true);
-
- mController.updateViewState(1f, View.VISIBLE);
-
- // Since we're disabled, we stay invisible
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_notKeyguardState_nothingUpdated() {
- mController.onViewAttached();
- updateStateToNotKeyguard();
-
- float oldAlpha = mKeyguardStatusBarView.getAlpha();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(oldAlpha);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_bypassNotEnabled_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(true);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(false);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_shouldNotListenForFace_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- when(mKeyguardUpdateMonitor.shouldListenForFace()).thenReturn(false);
- when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- onFinishedGoingToSleep();
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_panelExpandedHeightZero_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mShadeViewStateProvider.setPanelViewExpandedHeight(0);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dragProgressOne_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mShadeViewStateProvider.setLockscreenShadeDragProgress(1f);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemInfoFalse_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(false);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemInfoTrue_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(true);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemIconsFalse_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemIcons(false);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_disableSystemIconsTrue_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemIcons(true);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dozingTrue_flagOff_viewHidden() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setDozing(true);
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateViewState_dozingFalse_flagOff_viewShown() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setDozing(false);
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @EnableSceneContainer
- public void updateViewState_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setVisibility(View.GONE);
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.updateViewState();
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setVisibility(View.GONE);
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.updateViewState(0.789f, View.VISIBLE);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.GONE);
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void setAlpha_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mKeyguardStatusBarView.setAlpha(0.456f);
-
- mController.setAlpha(0.123f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.456f);
- }
-
- @Test
- @EnableSceneContainer
- public void setDozing_flagOn_doesNothing() {
- mController.init();
- mController.onViewAttached();
- updateStateToKeyguard();
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
-
- mController.setDozing(true);
- mController.updateViewState();
-
- // setDozing(true) should typically cause the view to hide. But since the flag is on, we
- // should ignore these set dozing calls and stay the same visibility.
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void setAlpha_explicitAlpha_setsExplicitAlpha() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setAlpha(0.5f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isEqualTo(0.5f);
- }
-
- @Test
- @DisableSceneContainer
- public void setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- mController.setAlpha(0.5f);
- mController.setAlpha(-1f);
-
- assertThat(mKeyguardStatusBarView.getAlpha()).isGreaterThan(0);
- assertThat(mKeyguardStatusBarView.getAlpha()).isNotEqualTo(0.5f);
- }
-
- // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
-
- @Test
- @DisableSceneContainer
- public void updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- mKeyguardStatusBarView.setVisibility(View.VISIBLE);
-
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
- mController.updateForHeadsUp(/* animate= */ false);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- @Test
- @DisableSceneContainer
- public void updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
- mController.onViewAttached();
- updateStateToKeyguard();
-
- // Start with the opposite state.
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(true);
- mController.updateForHeadsUp(/* animate= */ false);
-
- mShadeViewStateProvider.setShouldHeadsUpBeVisible(false);
- mController.updateForHeadsUp(/* animate= */ false);
-
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void testNewUserSwitcherDisablesAvatar_newUiOn() {
- // GIVEN the status bar user switcher chip is enabled
- when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(true);
-
- // WHEN the controller is created
- mController = createController();
-
- // THEN keyguard status bar view avatar is disabled
- assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isFalse();
- }
-
- @Test
- public void testNewUserSwitcherDisablesAvatar_newUiOff() {
- // GIVEN the status bar user switcher chip is disabled
- when(mStatusBarUserChipViewModel.getChipEnabled()).thenReturn(false);
-
- // WHEN the controller is created
- mController = createController();
-
- // THEN keyguard status bar view avatar is enabled
- assertThat(mKeyguardStatusBarView.isKeyguardUserAvatarEnabled()).isTrue();
- }
-
- @Test
- public void testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
- String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
- // GIVEN the setting is off
- when(mSecureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
- .thenReturn(0);
-
- // WHEN CollapsedStatusBarFragment builds the blocklist
- mController.updateBlockedIcons();
-
- // THEN status_bar_volume SHOULD be present in the list
- boolean contains = mController.getBlockedIcons().contains(str);
- assertTrue(contains);
- }
-
- @Test
- public void testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
- String str = mContext.getString(com.android.internal.R.string.status_bar_volume);
-
- // GIVEN the setting is ON
- when(mSecureSettings.getIntForUser(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0,
- UserHandle.USER_CURRENT))
- .thenReturn(1);
-
- // WHEN CollapsedStatusBarFragment builds the blocklist
- mController.updateBlockedIcons();
-
- // THEN status_bar_volume SHOULD NOT be present in the list
- boolean contains = mController.getBlockedIcons().contains(str);
- assertFalse(contains);
- }
-
- private void updateStateToNotKeyguard() {
- updateStatusBarState(SHADE);
- }
-
- private void updateStateToKeyguard() {
- updateStatusBarState(KEYGUARD);
- }
-
- private void updateStatusBarState(int state) {
- ArgumentCaptor<StatusBarStateController.StateListener> statusBarStateListenerCaptor =
- ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
- verify(mStatusBarStateController).addCallback(statusBarStateListenerCaptor.capture());
- StatusBarStateController.StateListener callback = statusBarStateListenerCaptor.getValue();
-
- callback.onStateChanged(state);
- }
-
- @Test
- @DisableSceneContainer
- public void animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
- mController.onViewAttached();
- updateStateToKeyguard();
- setDisableSystemInfo(true);
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
-
- mController.animateKeyguardStatusBarIn();
-
- // Since we're disabled, we don't actually animate in and stay invisible
- assertThat(mKeyguardStatusBarView.getVisibility()).isEqualTo(View.INVISIBLE);
- }
-
- /**
- * Calls {@link com.android.keyguard.KeyguardUpdateMonitorCallback#onFinishedGoingToSleep(int)}
- * to ensure values are updated properly.
- */
- private void onFinishedGoingToSleep() {
- ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateCallbackCaptor =
- ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
- verify(mKeyguardUpdateMonitor).registerCallback(keyguardUpdateCallbackCaptor.capture());
- KeyguardUpdateMonitorCallback callback = keyguardUpdateCallbackCaptor.getValue();
-
- callback.onFinishedGoingToSleep(0);
- }
-
- private void setDisableSystemInfo(boolean disabled) {
- CommandQueue.Callbacks callback = getCommandQueueCallback();
- int disabled1 = disabled ? DISABLE_SYSTEM_INFO : 0;
- callback.disable(mContext.getDisplayId(), disabled1, 0, false);
- }
-
- private void setDisableSystemIcons(boolean disabled) {
- CommandQueue.Callbacks callback = getCommandQueueCallback();
- int disabled2 = disabled ? DISABLE2_SYSTEM_ICONS : 0;
- callback.disable(mContext.getDisplayId(), 0, disabled2, false);
- }
-
- private CommandQueue.Callbacks getCommandQueueCallback() {
- ArgumentCaptor<CommandQueue.Callbacks> captor =
- ArgumentCaptor.forClass(CommandQueue.Callbacks.class);
- verify(mCommandQueue).addCallback(captor.capture());
- return captor.getValue();
- }
-
- private void runAllScheduled() {
- mBackgroundExecutor.runAllReady();
- mFakeExecutor.runAllReady();
- }
-
- private static class TestShadeViewStateProvider
- implements ShadeViewStateProvider {
-
- TestShadeViewStateProvider() {}
-
- private float mPanelViewExpandedHeight = 100f;
- private boolean mShouldHeadsUpBeVisible = false;
- private float mLockscreenShadeDragProgress = 0f;
-
- @Override
- public float getPanelViewExpandedHeight() {
- return mPanelViewExpandedHeight;
- }
-
- @Override
- public boolean shouldHeadsUpBeVisible() {
- return mShouldHeadsUpBeVisible;
- }
-
- @Override
- public float getLockscreenShadeDragProgress() {
- return mLockscreenShadeDragProgress;
- }
-
- public void setPanelViewExpandedHeight(float panelViewExpandedHeight) {
- this.mPanelViewExpandedHeight = panelViewExpandedHeight;
- }
-
- public void setShouldHeadsUpBeVisible(boolean shouldHeadsUpBeVisible) {
- this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible;
- }
-
- public void setLockscreenShadeDragProgress(float lockscreenShadeDragProgress) {
- this.mLockscreenShadeDragProgress = lockscreenShadeDragProgress;
- }
- }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
new file mode 100644
index 000000000000..b815c6ce0c51
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.kt
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2021 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.phone
+
+import android.app.StatusBarManager
+import android.graphics.Insets
+import android.os.UserHandle
+import android.os.UserManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
+import android.provider.Settings
+import android.testing.TestableLooper
+import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.LayoutInflater
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.CarrierTextController
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
+import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.DisableSceneContainer
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shade.ShadeViewStateProvider
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
+import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler
+import com.android.systemui.statusbar.phone.ui.StatusBarIconController
+import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.UserInfoController
+import com.android.systemui.statusbar.ui.viewmodel.keyguardStatusBarViewModel
+import com.android.systemui.statusbar.ui.viewmodel.statusBarUserChipViewModel
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper(setAsMainLooper = true)
+class KeyguardStatusBarViewControllerTest : SysuiTestCase() {
+ private lateinit var kosmos: Kosmos
+ private lateinit var testScope: TestScope
+
+ @Mock private lateinit var carrierTextController: CarrierTextController
+
+ @Mock private lateinit var configurationController: ConfigurationController
+
+ @Mock private lateinit var animationScheduler: SystemStatusAnimationScheduler
+
+ @Mock private lateinit var batteryController: BatteryController
+
+ @Mock private lateinit var userInfoController: UserInfoController
+
+ @Mock private lateinit var statusBarIconController: StatusBarIconController
+
+ @Mock private lateinit var iconManagerFactory: TintedIconManager.Factory
+
+ @Mock private lateinit var iconManager: TintedIconManager
+
+ @Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
+
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+ @Mock private lateinit var keyguardBypassController: KeyguardBypassController
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Mock private lateinit var biometricUnlockController: BiometricUnlockController
+
+ @Mock
+ private lateinit var statusBarContentInsetsProviderStore: StatusBarContentInsetsProviderStore
+
+ @Mock private lateinit var userManager: UserManager
+
+ @Captor
+ private lateinit var configurationListenerCaptor:
+ ArgumentCaptor<ConfigurationController.ConfigurationListener>
+
+ @Captor
+ private lateinit var keyguardCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
+
+ @Mock private lateinit var secureSettings: SecureSettings
+
+ @Mock private lateinit var commandQueue: CommandQueue
+
+ @Mock private lateinit var logger: KeyguardLogger
+
+ @Mock private lateinit var statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory
+
+ private lateinit var shadeViewStateProvider: TestShadeViewStateProvider
+
+ private lateinit var keyguardStatusBarView: KeyguardStatusBarView
+ private lateinit var controller: KeyguardStatusBarViewController
+ private val fakeExecutor = FakeExecutor(FakeSystemClock())
+ private val backgroundExecutor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var looper: TestableLooper
+
+ @Before
+ @Throws(Exception::class)
+ fun setup() {
+ looper = TestableLooper.get(this)
+ kosmos = testKosmos()
+ testScope = kosmos.testScope
+ shadeViewStateProvider = TestShadeViewStateProvider()
+
+ Mockito.`when`(
+ kosmos.statusBarContentInsetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ )
+ .thenReturn(Insets.of(0, 0, 0, 0))
+
+ MockitoAnnotations.initMocks(this)
+
+ Mockito.`when`(iconManagerFactory.create(ArgumentMatchers.any(), ArgumentMatchers.any()))
+ .thenReturn(iconManager)
+ Mockito.`when`(statusBarContentInsetsProviderStore.defaultDisplay)
+ .thenReturn(kosmos.statusBarContentInsetsProvider)
+ allowTestableLooperAsMainThread()
+ looper.runWithLooper {
+ keyguardStatusBarView =
+ Mockito.spy(
+ LayoutInflater.from(mContext).inflate(R.layout.keyguard_status_bar, null)
+ as KeyguardStatusBarView
+ )
+ Mockito.`when`(keyguardStatusBarView.getDisplay()).thenReturn(mContext.display)
+ }
+
+ controller = createController()
+ }
+
+ private fun createController(): KeyguardStatusBarViewController {
+ return KeyguardStatusBarViewController(
+ kosmos.testDispatcher,
+ keyguardStatusBarView,
+ carrierTextController,
+ configurationController,
+ animationScheduler,
+ batteryController,
+ userInfoController,
+ statusBarIconController,
+ iconManagerFactory,
+ batteryMeterViewController,
+ shadeViewStateProvider,
+ keyguardStateController,
+ keyguardBypassController,
+ keyguardUpdateMonitor,
+ kosmos.keyguardStatusBarViewModel,
+ biometricUnlockController,
+ kosmos.statusBarStateController,
+ statusBarContentInsetsProviderStore,
+ userManager,
+ kosmos.statusBarUserChipViewModel,
+ secureSettings,
+ commandQueue,
+ fakeExecutor,
+ backgroundExecutor,
+ logger,
+ statusOverlayHoverListenerFactory,
+ kosmos.communalSceneInteractor,
+ kosmos.glanceableHubToLockscreenTransitionViewModel,
+ kosmos.lockscreenToGlanceableHubTransitionViewModel,
+ )
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onViewAttached_updateUserSwitcherFlagEnabled_callbacksRegistered() {
+ controller.onViewAttached()
+
+ runAllScheduled()
+ Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onViewAttached_updateUserSwitcherFlagDisabled_callbacksRegistered() {
+ controller.onViewAttached()
+
+ Mockito.verify(configurationController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).addCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).addCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).addCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onConfigurationChanged_updateUserSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ runAllScheduled()
+ Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ configurationListenerCaptor.value.onConfigChanged(null)
+
+ runAllScheduled()
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onConfigurationChanged_updateUserSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ Mockito.verify(configurationController).addCallback(configurationListenerCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ configurationListenerCaptor.value.onConfigChanged(null)
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onKeyguardVisibilityChanged_userSwitcherFlagEnabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ runAllScheduled()
+ Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+
+ runAllScheduled()
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ @DisableFlags(Flags.FLAG_UPDATE_USER_SWITCHER_BACKGROUND)
+ fun onKeyguardVisibilityChanged_userSwitcherFlagDisabled_updatesUserSwitcherVisibility() {
+ controller.onViewAttached()
+ Mockito.verify(keyguardUpdateMonitor).registerCallback(keyguardCallbackCaptor.capture())
+ Mockito.clearInvocations(userManager)
+ Mockito.clearInvocations(keyguardStatusBarView)
+
+ keyguardCallbackCaptor.value.onKeyguardVisibilityChanged(true)
+ Mockito.verify(userManager).isUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ Mockito.verify(keyguardStatusBarView).setUserSwitcherEnabled(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun onViewDetached_callbacksUnregistered() {
+ // Set everything up first.
+ controller.onViewAttached()
+
+ controller.onViewDetached()
+
+ Mockito.verify(configurationController).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(animationScheduler).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(userInfoController).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(commandQueue).removeCallback(ArgumentMatchers.any())
+ Mockito.verify(statusBarIconController).removeIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun onViewReAttached_flagOff_iconManagerNotReRegistered() {
+ controller.onViewAttached()
+ controller.onViewDetached()
+ Mockito.reset(statusBarIconController)
+
+ controller.onViewAttached()
+
+ Mockito.verify(statusBarIconController, Mockito.never())
+ .addIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun onViewReAttached_flagOn_iconManagerReRegistered() {
+ controller.onViewAttached()
+ controller.onViewDetached()
+ Mockito.reset(statusBarIconController)
+
+ controller.onViewAttached()
+
+ Mockito.verify(statusBarIconController).addIconGroup(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_true_callbackAdded() {
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_false_callbackRemoved() {
+ // First set to true so that we know setting to false is a change in state.
+ controller.setBatteryListening(true)
+
+ controller.setBatteryListening(false)
+
+ Mockito.verify(batteryController).removeCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setBatteryListening_trueThenTrue_callbackAddedOnce() {
+ controller.setBatteryListening(true)
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setBatteryListening_true_flagOn_callbackNotAdded() {
+ controller.setBatteryListening(true)
+
+ Mockito.verify(batteryController, Mockito.never()).addCallback(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun updateTopClipping_viewClippingUpdated() {
+ val viewTop = 20
+ keyguardStatusBarView.top = viewTop
+ val notificationPanelTop = 30
+
+ controller.updateTopClipping(notificationPanelTop)
+
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top)
+ .isEqualTo(notificationPanelTop - viewTop)
+ }
+
+ @Test
+ fun setNotTopClipping_viewClippingUpdatedToZero() {
+ // Start out with some amount of top clipping.
+ controller.updateTopClipping(50)
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top).isGreaterThan(0)
+
+ controller.setNoTopClipping()
+
+ Truth.assertThat(keyguardStatusBarView.clipBounds.top).isEqualTo(0)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_alphaAndVisibilityGiven_viewUpdated() {
+ // Verify the initial values so we know the method triggers changes.
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(1f)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ val newAlpha = 0.5f
+ val newVisibility = View.INVISIBLE
+ controller.updateViewState(newAlpha, newVisibility)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(newAlpha)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(newVisibility)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_paramVisibleButIsDisabled_viewIsInvisible() {
+ controller.onViewAttached()
+ setDisableSystemIcons(true)
+
+ controller.updateViewState(1f, View.VISIBLE)
+
+ // Since we're disabled, we stay invisible
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_notKeyguardState_nothingUpdated() {
+ controller.onViewAttached()
+ updateStateToNotKeyguard()
+
+ val oldAlpha = keyguardStatusBarView.alpha
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(oldAlpha)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_bypassEnabledAndShouldListenForFace_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_bypassNotEnabled_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(true)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(false)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_shouldNotListenForFace_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ Mockito.`when`(keyguardUpdateMonitor.shouldListenForFace()).thenReturn(false)
+ Mockito.`when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+ onFinishedGoingToSleep()
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_panelExpandedHeightZero_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ shadeViewStateProvider.panelViewExpandedHeight = 0f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dragProgressOne_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ shadeViewStateProvider.lockscreenShadeDragProgress = 1f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemInfoFalse_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(false)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemInfoTrue_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(true)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemIconsFalse_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemIcons(false)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_disableSystemIconsTrue_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemIcons(true)
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dozingTrue_flagOff_viewHidden() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setDozing(true)
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateViewState_dozingFalse_flagOff_viewShown() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setDozing(false)
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateViewState_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.visibility = View.GONE
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.updateViewState()
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun updateViewStateWithAlphaAndVis_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.visibility = View.GONE
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.updateViewState(0.789f, View.VISIBLE)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.GONE)
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setAlpha_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ keyguardStatusBarView.alpha = 0.456f
+
+ controller.setAlpha(0.123f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.456f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun setDozing_flagOn_doesNothing() {
+ controller.init()
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+
+ controller.setDozing(true)
+ controller.updateViewState()
+
+ // setDozing(true) should typically cause the view to hide. But since the flag is on, we
+ // should ignore these set dozing calls and stay the same visibility.
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setAlpha_explicitAlpha_setsExplicitAlpha() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setAlpha(0.5f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isEqualTo(0.5f)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun setAlpha_explicitAlpha_thenMinusOneAlpha_setsAlphaBasedOnDefaultCriteria() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ controller.setAlpha(0.5f)
+ controller.setAlpha(-1f)
+
+ Truth.assertThat(keyguardStatusBarView.alpha).isGreaterThan(0)
+ Truth.assertThat(keyguardStatusBarView.alpha).isNotEqualTo(0.5f)
+ }
+
+ // TODO(b/195442899): Add more tests for #updateViewState once CLs are finalized.
+ @Test
+ @DisableSceneContainer
+ fun updateForHeadsUp_headsUpShouldBeVisible_viewHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ keyguardStatusBarView.visibility = View.VISIBLE
+
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun updateForHeadsUp_headsUpShouldNotBeVisible_viewShown() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+
+ // Start with the opposite state.
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(true)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ shadeViewStateProvider.setShouldHeadsUpBeVisible(false)
+ controller.updateForHeadsUp(/* animate= */ false)
+
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testNewUserSwitcherDisablesAvatar_newUiOn() =
+ testScope.runTest {
+ // GIVEN the status bar user switcher chip is enabled
+ kosmos.fakeUserRepository.isStatusBarUserChipEnabled = true
+
+ // WHEN the controller is created
+ controller = createController()
+
+ // THEN keyguard status bar view avatar is disabled
+ Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isFalse()
+ }
+
+ @Test
+ fun testNewUserSwitcherDisablesAvatar_newUiOff() {
+ // GIVEN the status bar user switcher chip is disabled
+ kosmos.fakeUserRepository.isStatusBarUserChipEnabled = false
+
+ // WHEN the controller is created
+ controller = createController()
+
+ // THEN keyguard status bar view avatar is enabled
+ Truth.assertThat(keyguardStatusBarView.isKeyguardUserAvatarEnabled).isTrue()
+ }
+
+ @Test
+ fun testBlockedIcons_obeysSettingForVibrateIcon_settingOff() {
+ val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+ // GIVEN the setting is off
+ Mockito.`when`(secureSettings.getInt(Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, 0))
+ .thenReturn(0)
+
+ // WHEN CollapsedStatusBarFragment builds the blocklist
+ controller.updateBlockedIcons()
+
+ // THEN status_bar_volume SHOULD be present in the list
+ val contains = controller.blockedIcons.contains(str)
+ Assert.assertTrue(contains)
+ }
+
+ @Test
+ fun testBlockedIcons_obeysSettingForVibrateIcon_settingOn() {
+ val str = mContext.getString(com.android.internal.R.string.status_bar_volume)
+
+ // GIVEN the setting is ON
+ Mockito.`when`(
+ secureSettings.getIntForUser(
+ Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON,
+ 0,
+ UserHandle.USER_CURRENT,
+ )
+ )
+ .thenReturn(1)
+
+ // WHEN CollapsedStatusBarFragment builds the blocklist
+ controller.updateBlockedIcons()
+
+ // THEN status_bar_volume SHOULD NOT be present in the list
+ val contains = controller.blockedIcons.contains(str)
+ Assert.assertFalse(contains)
+ }
+
+ private fun updateStateToNotKeyguard() {
+ updateStatusBarState(StatusBarState.SHADE)
+ }
+
+ private fun updateStateToKeyguard() {
+ updateStatusBarState(StatusBarState.KEYGUARD)
+ }
+
+ private fun updateStatusBarState(state: Int) {
+ kosmos.statusBarStateController.setState(state)
+ }
+
+ @Test
+ @DisableSceneContainer
+ fun animateKeyguardStatusBarIn_isDisabled_viewStillHidden() {
+ controller.onViewAttached()
+ updateStateToKeyguard()
+ setDisableSystemInfo(true)
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+
+ controller.animateKeyguardStatusBarIn()
+
+ // Since we're disabled, we don't actually animate in and stay invisible
+ Truth.assertThat(keyguardStatusBarView.visibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun animateToGlanceableHub_affectsAlpha() =
+ testScope.runTest {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ Truth.assertThat(keyguardStatusBarView.getAlpha()).isEqualTo(transitionAlphaAmount)
+ }
+
+ @Test
+ fun animateToGlanceableHub_alphaResetOnCommunalNotShowing() =
+ testScope.runTest {
+ controller.init()
+ val transitionAlphaAmount = .5f
+ ViewUtils.attachView(keyguardStatusBarView)
+ looper.processAllMessages()
+ updateStateToKeyguard()
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Communal)
+ runCurrent()
+ controller.updateCommunalAlphaTransition(transitionAlphaAmount)
+ kosmos.fakeCommunalSceneRepository.snapToScene(CommunalScenes.Blank)
+ runCurrent()
+ Truth.assertThat(keyguardStatusBarView.getAlpha()).isNotEqualTo(transitionAlphaAmount)
+ }
+
+ /**
+ * Calls [com.android.keyguard.KeyguardUpdateMonitorCallback.onFinishedGoingToSleep] to ensure
+ * values are updated properly.
+ */
+ private fun onFinishedGoingToSleep() {
+ val keyguardUpdateCallbackCaptor =
+ ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+ Mockito.verify(keyguardUpdateMonitor)
+ .registerCallback(keyguardUpdateCallbackCaptor.capture())
+ val callback = keyguardUpdateCallbackCaptor.value
+
+ callback.onFinishedGoingToSleep(0)
+ }
+
+ private fun setDisableSystemInfo(disabled: Boolean) {
+ val callback = commandQueueCallback
+ val disabled1 = if (disabled) StatusBarManager.DISABLE_SYSTEM_INFO else 0
+ callback.disable(mContext.displayId, disabled1, 0, false)
+ }
+
+ private fun setDisableSystemIcons(disabled: Boolean) {
+ val callback = commandQueueCallback
+ val disabled2 = if (disabled) StatusBarManager.DISABLE2_SYSTEM_ICONS else 0
+ callback.disable(mContext.displayId, 0, disabled2, false)
+ }
+
+ private val commandQueueCallback: CommandQueue.Callbacks
+ get() {
+ val captor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+ Mockito.verify(commandQueue).addCallback(captor.capture())
+ return captor.value
+ }
+
+ private fun runAllScheduled() {
+ backgroundExecutor.runAllReady()
+ fakeExecutor.runAllReady()
+ }
+
+ private class TestShadeViewStateProvider : ShadeViewStateProvider {
+ override var panelViewExpandedHeight: Float = 100f
+ private var mShouldHeadsUpBeVisible = false
+ override var lockscreenShadeDragProgress: Float = 0f
+
+ override fun shouldHeadsUpBeVisible(): Boolean {
+ return mShouldHeadsUpBeVisible
+ }
+
+ fun setShouldHeadsUpBeVisible(shouldHeadsUpBeVisible: Boolean) {
+ this.mShouldHeadsUpBeVisible = shouldHeadsUpBeVisible
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 88ec18dd65f3..9099334b360c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -48,12 +48,10 @@ import com.android.keyguard.TestScopeProvider;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
-import com.android.systemui.settings.FakeDisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
import com.android.systemui.statusbar.data.model.StatusBarMode;
-import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.FakeStatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.util.kotlin.JavaAdapter;
import kotlinx.coroutines.test.TestScope;
@@ -81,8 +79,8 @@ public class LightBarControllerTest extends SysuiTestCase {
private SysuiDarkIconDispatcher mStatusBarIconController;
private LightBarController mLightBarController;
private final TestScope mTestScope = TestScopeProvider.getTestScope();
- private final FakeStatusBarModeRepository mStatusBarModeRepository =
- new FakeStatusBarModeRepository();
+ private final FakeStatusBarModePerDisplayRepository mStatusBarModeRepository =
+ new FakeStatusBarModePerDisplayRepository();
@Before
public void setup() {
@@ -92,15 +90,16 @@ public class LightBarControllerTest extends SysuiTestCase {
mLightBarTransitionsController = mock(LightBarTransitionsController.class);
when(mStatusBarIconController.getTransitionsController()).thenReturn(
mLightBarTransitionsController);
- mLightBarController = new LightBarController(
- mContext,
- new JavaAdapter(mTestScope),
+ mLightBarController = new LightBarControllerImpl(
+ mContext.getDisplayId(),
+ mTestScope,
mStatusBarIconController,
mock(BatteryController.class),
mock(NavigationModeController.class),
mStatusBarModeRepository,
mock(DumpManager.class),
- new FakeDisplayTracker(mContext));
+ mTestScope.getCoroutineContext(),
+ mock(BiometricUnlockController.class));
mLightBarController.start();
}
@@ -121,7 +120,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -142,7 +141,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -165,7 +164,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -190,7 +189,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, thirdBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -214,7 +213,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(0 /* appearance */, secondBounds)
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -231,7 +230,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -249,7 +248,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(0, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -266,7 +265,7 @@ public class LightBarControllerTest extends SysuiTestCase {
new AppearanceRegion(APPEARANCE_LIGHT_STATUS_BARS, new Rect(0, 0, 1, 1))
);
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -276,7 +275,7 @@ public class LightBarControllerTest extends SysuiTestCase {
reset(mStatusBarIconController);
// WHEN the same appearance regions but different status bar mode is sent
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.LIGHTS_OUT_TRANSPARENT,
STATUS_BAR_BOUNDS,
@@ -298,7 +297,7 @@ public class LightBarControllerTest extends SysuiTestCase {
/* start= */ new Rect(0, 0, 10, 10),
/* end= */ new Rect(0, 0, 20, 20));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
startingBounds,
@@ -311,7 +310,7 @@ public class LightBarControllerTest extends SysuiTestCase {
BoundsPair newBounds = new BoundsPair(
/* start= */ new Rect(0, 0, 30, 30),
/* end= */ new Rect(0, 0, 40, 40));
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance().setValue(
+ mStatusBarModeRepository.getStatusBarAppearance().setValue(
new StatusBarAppearance(
StatusBarMode.TRANSPARENT,
newBounds,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
index e0d9fac0eba5..110dec6c33aa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerViaListenerTest.kt
@@ -36,7 +36,6 @@ import com.android.systemui.Flags.FLAG_STATUS_BAR_USE_REPOS_FOR_CALL_CHIP
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
@@ -58,7 +57,6 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
-import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -144,6 +142,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
controller.start()
controller.addCallback(mockOngoingCallListener)
controller.setChipView(chipView)
+ onTeardown { controller.tearDownChipView() }
val collectionListenerCaptor = ArgumentCaptor.forClass(NotifCollectionListener::class.java)
verify(notificationCollection).addCollectionListener(collectionListenerCaptor.capture())
@@ -153,11 +152,6 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
.thenReturn(PROC_STATE_INVISIBLE)
}
- @After
- fun tearDown() {
- controller.tearDownChipView()
- }
-
@Test
fun onEntryUpdated_isOngoingCallNotif_listenerAndRepoNotified() {
val notification = NotificationEntryBuilder(createOngoingCallNotifEntry())
@@ -224,7 +218,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -241,7 +235,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -257,7 +251,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
notifCollectionListener.onEntryUpdated(notification.build())
chipView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
)
assertThat(chipView.findViewById<View>(R.id.ongoing_activity_chip_time)?.measuredWidth)
@@ -668,7 +662,7 @@ class OngoingCallControllerViaListenerTest : SysuiTestCase() {
private fun createCallNotifEntry(
callStyle: Notification.CallStyle,
- nullContentIntent: Boolean = false
+ nullContentIntent: Boolean = false,
): NotificationEntry {
val notificationEntryBuilder = NotificationEntryBuilder()
notificationEntryBuilder.modifyNotification(context).style = callStyle
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
index fc1ea227f1bd..19d5a16deabd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcherTest.kt
@@ -66,6 +66,7 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() {
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
private val demoDataSource =
mock<DemoDeviceBasedSatelliteDataSource>().also {
@@ -80,7 +81,11 @@ class DeviceBasedSatelliteRepositorySwitcherTest : SysuiTestCase() {
)
}
private val demoImpl =
- DemoDeviceBasedSatelliteRepository(demoDataSource, testScope.backgroundScope)
+ DemoDeviceBasedSatelliteRepository(
+ demoDataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
private val underTest =
DeviceBasedSatelliteRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
index 87693891a281..a70881aedeb6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepositoryTest.kt
@@ -58,7 +58,12 @@ class DemoDeviceBasedSatelliteRepositoryTest : SysuiTestCase() {
whenever(it.satelliteEvents).thenReturn(fakeSatelliteEvents)
}
- underTest = DemoDeviceBasedSatelliteRepository(dataSource, testScope.backgroundScope)
+ underTest =
+ DemoDeviceBasedSatelliteRepository(
+ dataSource,
+ testScope.backgroundScope,
+ context.resources,
+ )
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
index 55460bd5b801..41fa9e7be81b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/FakeDeviceBasedSatelliteRepository.kt
@@ -28,4 +28,6 @@ class FakeDeviceBasedSatelliteRepository() : DeviceBasedSatelliteRepository {
override val signalStrength = MutableStateFlow(0)
override val isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+ override var isOpportunisticSatelliteIconEnabled: Boolean = true
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
index e7e496938033..509aa7ad67fd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
@@ -172,6 +172,26 @@ class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
}
@Test
+ fun icon_null_allOosAndConfigIsFalse() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.icon)
+
+ // GIVEN config for opportunistic icon is false
+ repo.isOpportunisticSatelliteIconEnabled = false
+
+ // GIVEN all icons are OOS
+ val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
+ i1.isInService.value = false
+ i1.isEmergencyOnly.value = false
+
+ // GIVEN apm is disabled
+ airplaneModeRepository.setIsAirplaneMode(false)
+
+ // THEN icon is null because it is not allowed
+ assertThat(latest).isNull()
+ }
+
+ @Test
fun icon_null_isEmergencyOnly() =
testScope.runTest {
val latest by collectLastValue(underTest.icon)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 53e033ef7c93..e7ca1dd3e4b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -51,6 +51,8 @@ import android.graphics.Color;
import android.os.Handler;
import android.os.UserHandle;
import android.os.UserManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.provider.Settings;
import androidx.annotation.VisibleForTesting;
@@ -561,6 +563,113 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaper_shouldUpdateTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings).putStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture(),
+ anyInt());
+
+ verify(mThemeOverlayApplier)
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_homeWallpaperWithSameColor_shouldKeepThemeAndReapply() {
+ // Shouldn't ask for a new theme when the colors of the last applied wallpaper change
+ // with the same specified system palette one.
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(0xffa16b00), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_SYSTEM,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ // Apply overlay by existing theme from secure setting
+ verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
+ public void onWallpaperColorsChanged_lockWallpaper_shouldKeepTheme() {
+ // Should ask for a new theme when the colors of the last applied wallpaper change
+ WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
+ Color.valueOf(Color.BLUE), null);
+
+ String jsonString =
+ "{\"android.theme.customization.color_source\":\"home_wallpaper\","
+ + "\"android.theme.customization.system_palette\":\"A16B00\","
+ + "\"android.theme.customization.accent_color\":\"A16B00\","
+ + "\"android.theme.customization.color_index\":\"2\"}";
+
+ when(mSecureSettings.getStringForUser(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), anyInt()))
+ .thenReturn(jsonString);
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, USER_SYSTEM))
+ .thenReturn(1);
+ // Set LOCK wallpaper as the last applied one to verify that theme is no longer based on
+ // latest wallpaper
+ when(mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, USER_SYSTEM))
+ .thenReturn(2);
+
+ mColorsListener.getValue().onColorsChanged(mainColors, WallpaperManager.FLAG_LOCK,
+ USER_SYSTEM);
+
+ ArgumentCaptor<String> updatedSetting = ArgumentCaptor.forClass(String.class);
+ verify(mSecureSettings, never()).putString(
+ eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), updatedSetting.capture());
+
+ verify(mThemeOverlayApplier, never())
+ .applyCurrentUserOverlays(any(), any(), anyInt(), any(), any());
+ }
+
+ @Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_resetThemeWhenFromLatestWallpaper() {
// Should ask for a new theme when the colors of the last applied wallpaper change
WallpaperColors mainColors = new WallpaperColors(Color.valueOf(Color.RED),
@@ -594,6 +703,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeWhenFromLatestWallpaperAndSpecifiedColor() {
// Shouldn't ask for a new theme when the colors of the last applied wallpaper change
// with the same specified system palette one.
@@ -627,6 +737,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_NEW_CUSTOMIZATION_PICKER_UI)
public void onWallpaperColorsChanged_keepThemeIfNotLatestWallpaper() {
// Shouldn't ask for a new theme when the colors of the wallpaper that is not the last
// applied one change
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
index a0cfab4d2160..e88dbd27fd37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepositoryTest.kt
@@ -22,11 +22,10 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,70 +41,127 @@ import org.junit.runner.RunWith
class UserAwareSecureSettingsRepositoryTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val dispatcher = kosmos.testDispatcher
private val testScope = kosmos.testScope
private val secureSettings = kosmos.fakeSettings
- private val userRepository = Kosmos().fakeUserRepository
- private lateinit var repository: UserAwareSecureSettingsRepository
+ private val userRepository = kosmos.fakeUserRepository
+ private lateinit var underTest: UserAwareSecureSettingsRepository
@Before
fun setup() {
- repository =
- UserAwareSecureSettingsRepositoryImpl(
- secureSettings,
- userRepository,
- dispatcher,
- )
+ underTest = kosmos.userAwareSecureSettingsRepository
+
userRepository.setUserInfos(USER_INFOS)
- setSettingValueForUser(enabled = true, userInfo = SETTING_ENABLED_USER)
- setSettingValueForUser(enabled = false, userInfo = SETTING_DISABLED_USER)
+
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, true, USER_1.id)
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_2.id)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 1337, USER_1.id)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 818, USER_2.id)
}
@Test
- fun settingEnabledEmitsValueForCurrentUser() {
+ fun boolSetting_emitsInitialValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
+ userRepository.setSelectedUserInfo(USER_1)
- val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
assertThat(enabled).isTrue()
}
}
@Test
- fun settingEnabledEmitsNewValueWhenSettingChanges() {
+ fun boolSetting_whenSettingChanges_emitsNewValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectValues(repository.boolSettingForActiveUser(SETTING_NAME))
+ userRepository.setSelectedUserInfo(USER_1)
+ val enabled by collectValues(underTest.boolSetting(BOOL_SETTING_NAME, false))
runCurrent()
- setSettingValueForUser(enabled = false, userInfo = SETTING_ENABLED_USER)
+ secureSettings.putBoolForUser(BOOL_SETTING_NAME, false, USER_1.id)
assertThat(enabled).containsExactly(true, false).inOrder()
}
}
@Test
- fun settingEnabledEmitsValueForNewUserWhenUserChanges() {
+ fun boolSetting_whenWhenUserChanges_emitsNewValue() {
testScope.runTest {
- userRepository.setSelectedUserInfo(SETTING_ENABLED_USER)
- val enabled by collectLastValue(repository.boolSettingForActiveUser(SETTING_NAME))
+ userRepository.setSelectedUserInfo(USER_1)
+ val enabled by collectLastValue(underTest.boolSetting(BOOL_SETTING_NAME, false))
runCurrent()
- userRepository.setSelectedUserInfo(SETTING_DISABLED_USER)
+ userRepository.setSelectedUserInfo(USER_2)
assertThat(enabled).isFalse()
}
}
- private fun setSettingValueForUser(enabled: Boolean, userInfo: UserInfo) {
- secureSettings.putBoolForUser(SETTING_NAME, enabled, userInfo.id)
+ @Test
+ fun intSetting_emitsInitialValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+
+ val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+
+ assertThat(number).isEqualTo(1337)
+ }
+ }
+
+ @Test
+ fun intSetting_whenSettingChanges_emitsNewValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ val number by collectValues(underTest.intSetting(INT_SETTING_NAME, 0))
+ runCurrent()
+
+ secureSettings.putIntForUser(INT_SETTING_NAME, 1338, USER_1.id)
+
+ assertThat(number).containsExactly(1337, 1338).inOrder()
+ }
+ }
+
+ @Test
+ fun intSetting_whenWhenUserChanges_emitsNewValue() {
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ val number by collectLastValue(underTest.intSetting(INT_SETTING_NAME, 0))
+ runCurrent()
+
+ userRepository.setSelectedUserInfo(USER_2)
+
+ assertThat(number).isEqualTo(818)
+ }
}
+ @Test
+ fun getInt_returnsInitialValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(1337)
+ }
+
+ @Test
+ fun getInt_whenSettingChanges_returnsNewValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_1)
+ secureSettings.putIntForUser(INT_SETTING_NAME, 999, USER_1.id)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(999)
+ }
+
+ @Test
+ fun getInt_whenUserChanges_returnsThatUserValue() =
+ testScope.runTest {
+ userRepository.setSelectedUserInfo(USER_2)
+
+ assertThat(underTest.getInt(INT_SETTING_NAME, 0)).isEqualTo(818)
+ }
+
private companion object {
- const val SETTING_NAME = "SETTING_NAME"
- val SETTING_ENABLED_USER = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
- val SETTING_DISABLED_USER = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
- val USER_INFOS = listOf(SETTING_ENABLED_USER, SETTING_DISABLED_USER)
+ const val BOOL_SETTING_NAME = "BOOL_SETTING_NAME"
+ const val INT_SETTING_NAME = "INT_SETTING_NAME"
+ val USER_1 = UserInfo(/* id= */ 0, "user1", /* flags= */ 0)
+ val USER_2 = UserInfo(/* id= */ 1, "user2", /* flags= */ 0)
+ val USER_INFOS = listOf(USER_1, USER_2)
}
}
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 65005f840598..572f063c20c4 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -32,6 +32,34 @@
android:id="@+id/min_edge_guideline"
app:layout_constraintGuide_begin="@dimen/overlay_action_container_minimum_edge_spacing"
android:orientation="vertical"/>
+ <!-- This toast-like indication layout was forked from text_toast.xml and will have the same
+ appearance as system toast. -->
+ <FrameLayout
+ android:id="@+id/indication_container"
+ android:visibility="gone"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:maxWidth="@*android:dimen/toast_width"
+ android:background="@android:drawable/toast_frame"
+ android:elevation="@*android:dimen/toast_elevation"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:layout_marginEnd="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
+ android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent">
+ <TextView
+ android:id="@+id/indication_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:textAppearance="@*android:style/TextAppearance.Toast"/>
+ </FrameLayout>
<!-- Negative horizontal margin because this container background must render beyond the thing
it's constrained by (the actions themselves). -->
<FrameLayout
@@ -47,7 +75,7 @@
app:layout_constraintStart_toStartOf="@id/min_edge_guideline"
app:layout_constraintTop_toTopOf="@id/actions_container"
app:layout_constraintEnd_toEndOf="@id/actions_container"
- app:layout_constraintBottom_toBottomOf="parent"/>
+ app:layout_constraintBottom_toTopOf="@id/indication_container"/>
<HorizontalScrollView
android:id="@+id/actions_container"
android:layout_width="0dp"
@@ -144,7 +172,7 @@
android:visibility="gone"
android:elevation="7dp"
android:padding="8dp"
- app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/indication_container"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="@dimen/overlay_action_container_margin_horizontal"
android:layout_marginBottom="@dimen/overlay_action_container_margin_bottom"
diff --git a/packages/SystemUI/res/layout/volume_ringer_drawer.xml b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
index 9b3af52e9704..7c266e60b503 100644
--- a/packages/SystemUI/res/layout/volume_ringer_drawer.xml
+++ b/packages/SystemUI/res/layout/volume_ringer_drawer.xml
@@ -65,7 +65,7 @@
android:background="@drawable/volume_drawer_selection_bg"
android:contentDescription="@string/volume_ringer_change"
android:gravity="center"
- android:padding="10dp"
+ android:padding="@dimen/volume_dialog_ringer_horizontal_padding"
android:src="@drawable/ic_volume_media"
android:tint="?androidprv:attr/materialColorOnPrimary" />
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index d4a52c3aeafb..c8ef093c40db 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -383,6 +383,10 @@
<!-- Whether to show activity indicators in the status bar -->
<bool name="config_showActivity">false</bool>
+ <!-- Whether to show the opportunistic satellite icon. When true, an icon will show to indicate
+ satellite capabilities when all other connections are out of service. -->
+ <bool name="config_showOpportunisticSatelliteIcon">true</bool>
+
<!-- Whether or not to show the notification shelf that houses the icons of notifications that
have been scrolled off-screen. -->
<bool name="config_showNotificationShelf">true</bool>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 1766cdf8c804..7b50582efe64 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1518,7 +1518,7 @@
<string name="no_unseen_notif_text">No new notifications</string>
<!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=60] -->
- <string name="adaptive_notification_edu_hun_title">Notification cooldown is on</string>
+ <string name="adaptive_notification_edu_hun_title">Notification cooldown is now on</string>
<!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
<string name="adaptive_notification_edu_hun_text">Your device volume and alerts are reduced automatically for up to 2 minutes when you get too many notifications at once.</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 7ec977a8d6aa..9e8cabf141ed 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -243,10 +243,6 @@ public class Task {
public Rect appBounds;
- // Last snapshot data, only used for recent tasks
- public ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData lastSnapshotData =
- new ActivityManager.RecentTaskInfo.PersistedTaskSnapshotData();
-
@ViewDebug.ExportedProperty(category="recents")
public boolean isVisible;
@@ -283,7 +279,6 @@ public class Task {
public Task(Task other) {
this(other.key, other.colorPrimary, other.colorBackground, other.isDockable,
other.isLocked, other.taskDescription, other.topActivity);
- lastSnapshotData.set(other.lastSnapshotData);
positionInParent = other.positionInParent;
appBounds = other.appBounds;
isVisible = other.isVisible;
@@ -315,33 +310,10 @@ public class Task {
: key.baseIntent.getComponent();
}
- public void setLastSnapshotData(ActivityManager.RecentTaskInfo rawTask) {
- lastSnapshotData.set(rawTask.lastSnapshotData);
- }
-
public TaskKey getKey() {
return key;
}
- /**
- * Returns the visible width to height ratio. Returns 0f if snapshot data is not available.
- */
- public float getVisibleThumbnailRatio(boolean clipInsets) {
- if (lastSnapshotData.taskSize == null || lastSnapshotData.contentInsets == null) {
- return 0f;
- }
-
- float availableWidth = lastSnapshotData.taskSize.x;
- float availableHeight = lastSnapshotData.taskSize.y;
- if (clipInsets) {
- availableWidth -=
- (lastSnapshotData.contentInsets.left + lastSnapshotData.contentInsets.right);
- availableHeight -=
- (lastSnapshotData.contentInsets.top + lastSnapshotData.contentInsets.bottom);
- }
- return availableWidth / availableHeight;
- }
-
@Override
public boolean equals(Object o) {
if (o == this) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 8b593701540b..eda07cfe8d91 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -121,6 +121,7 @@ import com.android.settingslib.WirelessUtils;
import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dumpable;
+import com.android.systemui.Flags;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
@@ -473,6 +474,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
+ @Deprecated
private final SparseBooleanArray mUserIsUnlocked = new SparseBooleanArray();
private final SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
private final SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
@@ -2688,7 +2690,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @see Intent#ACTION_USER_UNLOCKED
*/
public boolean isUserUnlocked(int userId) {
- return mUserIsUnlocked.get(userId);
+ if (Flags.userEncryptedSource()) {
+ boolean userStorageUnlocked = mUserManager.isUserUnlocked(userId);
+ mLogger.logUserStorageUnlocked(userId, userStorageUnlocked);
+ return userStorageUnlocked;
+ } else {
+ return mUserIsUnlocked.get(userId);
+ }
}
/**
@@ -4213,7 +4221,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
pw.println("ActiveUnlockRunning="
+ mTrustManager.isActiveUnlockRunning(mSelectedUserInteractor.getSelectedUserId()));
- pw.println("userUnlockedCache[userid=" + userId + "]=" + isUserUnlocked(userId));
+ pw.println("userUnlockedCache[userid=" + userId + "]=" + mUserIsUnlocked.get(userId));
pw.println("actualUserUnlocked[userid=" + userId + "]="
+ mUserManager.isUserUnlocked(userId));
new DumpsysTableLogger(
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index bebfd859f9ed..cd19aaac6831 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -116,7 +116,7 @@ constructor(
fun logUpdateLockScreenUserLockedMsg(
userId: Int,
- userUnlocked: Boolean,
+ userStorageUnlocked: Boolean,
encryptedOrLockdown: Boolean,
) {
buffer.log(
@@ -124,12 +124,12 @@ constructor(
LogLevel.DEBUG,
{
int1 = userId
- bool1 = userUnlocked
+ bool1 = userStorageUnlocked
bool2 = encryptedOrLockdown
},
{
"updateLockScreenUserLockedMsg userId=$int1 " +
- "userUnlocked:$bool1 encryptedOrLockdown:$bool2"
+ "userStorageUnlocked:$bool1 encryptedOrLockdown:$bool2"
}
)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 12fc3c262367..b3ddde38509a 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -582,6 +582,18 @@ constructor(@KeyguardUpdateMonitorLog private val logBuffer: LogBuffer) {
logBuffer.log(TAG, DEBUG, { int1 = userId }, { "userUnlocked userId: $int1" })
}
+ fun logUserStorageUnlocked(userId: Int, result: Boolean) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ {
+ int1 = userId
+ bool1 = result
+ },
+ { "Invoked UserManager#isUserUnlocked $int1, result: $bool1" },
+ )
+ }
+
fun logUserStopped(userId: Int, isUnlocked: Boolean) {
logBuffer.log(
TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
index 5414b623ff97..39fd44a83c58 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/CaptioningRepository.kt
@@ -63,6 +63,7 @@ constructor(
private val captioningManager: StateFlow<CaptioningManager?> =
userRepository.selectedUser
.map { userScopedCaptioningManagerProvider.forUser(it.userInfo.userHandle) }
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.WhileSubscribed(), null)
override val captioningModel: StateFlow<CaptioningModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 12b5fc01845c..b491c94db151 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -27,6 +27,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AlertDialog;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
@@ -315,6 +316,16 @@ public class AuthContainerView extends LinearLayout
mBiometricCallback = new BiometricCallback();
mMSDLPlayer = msdlPlayer;
+ // Listener for when device locks from adaptive auth, dismiss prompt
+ getContext().getSystemService(KeyguardManager.class).addKeyguardLockedStateListener(
+ getContext().getMainExecutor(),
+ isKeyguardLocked -> {
+ if (isKeyguardLocked) {
+ onStartedGoingToSleep();
+ }
+ }
+ );
+
final BiometricModalities biometricModalities = new BiometricModalities(
Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index ba51d02fd288..68ec0f2d57ef 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -25,6 +25,7 @@ import android.hardware.fingerprint.FingerprintManager
import android.hardware.fingerprint.FingerprintSensorProperties
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.util.Log
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.biometrics.shared.model.SensorStrength
import com.android.systemui.biometrics.shared.model.toSensorStrength
@@ -91,13 +92,14 @@ constructor(
trySendWithFailureLogging(
DEFAULT_PROPS,
TAG,
- "no registered sensors, use default props"
+ "no registered sensors, use default props",
)
} else {
+ Log.d(TAG, "onAllAuthenticatorsRegistered $sensors")
trySendWithFailureLogging(
sensors[0],
TAG,
- "update properties on authenticators registered"
+ "update properties on authenticators registered",
)
}
}
@@ -160,7 +162,7 @@ constructor(
FingerprintSensorProperties.TYPE_UNKNOWN,
false /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
- listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
)
private val DEFAULT_PROPS =
FingerprintSensorPropertiesInternal(
@@ -171,7 +173,7 @@ constructor(
FingerprintSensorProperties.TYPE_UNKNOWN,
false /* halControlsIllumination */,
true /* resetLockoutRequiresHardwareAuthToken */,
- listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+ listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 18a7739f12ab..abbbd730c47e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -48,14 +48,14 @@ constructor(
private val authController: AuthController,
private val selectedUserInteractor: SelectedUserInteractor,
private val fingerprintManager: FingerprintManager?,
- @Application scope: CoroutineScope
+ @Application scope: CoroutineScope,
) {
private fun calculateIconSize(): Int {
val pixelPitch = context.resources.getFloat(R.dimen.pixel_pitch)
if (pixelPitch <= 0) {
Log.e(
"UdfpsOverlayInteractor",
- "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device."
+ "invalid pixelPitch: $pixelPitch. Pixel pitch must be updated per device.",
)
}
return (context.resources.getFloat(R.dimen.udfps_icon_size) / pixelPitch).toInt()
@@ -83,12 +83,11 @@ constructor(
/** Sets whether Udfps overlay should handle touches */
fun setHandleTouches(shouldHandle: Boolean = true) {
- if (authController.isUdfpsSupported
- && shouldHandle != _shouldHandleTouches.value) {
+ if (authController.isUdfpsSupported && shouldHandle != _shouldHandleTouches.value) {
fingerprintManager?.setIgnoreDisplayTouches(
requestId.value,
authController.udfpsProps!!.get(0).sensorId,
- !shouldHandle
+ !shouldHandle,
)
}
_shouldHandleTouches.value = shouldHandle
@@ -107,10 +106,11 @@ constructor(
override fun onUdfpsLocationChanged(
udfpsOverlayParams: UdfpsOverlayParams
) {
+ Log.d(TAG, "udfpsOverlayParams updated $udfpsOverlayParams")
trySendWithFailureLogging(
udfpsOverlayParams,
TAG,
- "update udfpsOverlayParams"
+ "update udfpsOverlayParams",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
new file mode 100644
index 000000000000..ddd6bc9ef16b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationCallback.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.clipboardoverlay
+
+/** Interface for listening to indication text changed from [ClipboardIndicationProvider]. */
+interface ClipboardIndicationCallback {
+
+ /** Notifies the indication text changed. */
+ fun onIndicationTextChanged(text: CharSequence)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
new file mode 100644
index 000000000000..be3272369d46
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProvider.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.clipboardoverlay
+
+/** Interface to provide the clipboard indication to be shown under the overlay. */
+interface ClipboardIndicationProvider {
+
+ /**
+ * Gets the indication text async.
+ *
+ * @param callback callback for getting the indication text.
+ */
+ fun getIndicationText(callback: ClipboardIndicationCallback)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
new file mode 100644
index 000000000000..da94d5b518b7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardIndicationProviderImpl.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.clipboardoverlay
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+open class ClipboardIndicationProviderImpl @Inject constructor() : ClipboardIndicationProvider {
+
+ override fun getIndicationText(callback: ClipboardIndicationCallback) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 65c01ed9eecd..ac747845267c 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -21,6 +21,7 @@ import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
import static com.android.systemui.Flags.clipboardImageTimeout;
import static com.android.systemui.Flags.clipboardSharedTransitions;
+import static com.android.systemui.Flags.showClipboardIndication;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -99,6 +100,7 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
private final ClipboardTransitionExecutor mTransitionExecutor;
private final ClipboardOverlayView mView;
+ private final ClipboardIndicationProvider mClipboardIndicationProvider;
private Runnable mOnSessionCompleteListener;
private Runnable mOnRemoteCopyTapped;
@@ -173,6 +175,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
}
};
+ private ClipboardIndicationCallback mIndicationCallback = new ClipboardIndicationCallback() {
+ @Override
+ public void onIndicationTextChanged(@NonNull CharSequence text) {
+ mView.setIndicationText(text);
+ }
+ };
+
@Inject
public ClipboardOverlayController(@OverlayWindowContext Context context,
ClipboardOverlayView clipboardOverlayView,
@@ -185,11 +194,13 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
@Background Executor bgExecutor,
ClipboardImageLoader clipboardImageLoader,
ClipboardTransitionExecutor transitionExecutor,
+ ClipboardIndicationProvider clipboardIndicationProvider,
UiEventLogger uiEventLogger) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
mClipboardImageLoader = clipboardImageLoader;
mTransitionExecutor = transitionExecutor;
+ mClipboardIndicationProvider = clipboardIndicationProvider;
mClipboardLogger = new ClipboardLogger(uiEventLogger);
@@ -288,6 +299,9 @@ public class ClipboardOverlayController implements ClipboardListener.ClipboardOv
boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
mClipboardModel = model;
mClipboardLogger.setClipSource(mClipboardModel.getSource());
+ if (showClipboardIndication()) {
+ mClipboardIndicationProvider.getIndicationText(mIndicationCallback);
+ }
if (clipboardImageTimeout()) {
if (shouldAnimate) {
reset();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
index 1762d82b3237..7e4d76280909 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayView.java
@@ -18,6 +18,8 @@ package com.android.systemui.clipboardoverlay;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.systemui.Flags.showClipboardIndication;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -53,6 +55,7 @@ import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -103,6 +106,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
private View mShareChip;
private View mRemoteCopyChip;
private View mActionContainerBackground;
+ private View mIndicationContainer;
+ private TextView mIndicationText;
private View mDismissButton;
private LinearLayout mActionContainer;
private ClipboardOverlayCallbacks mClipboardCallbacks;
@@ -136,6 +141,8 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mShareChip = requireViewById(R.id.share_chip);
mRemoteCopyChip = requireViewById(R.id.remote_copy_chip);
mDismissButton = requireViewById(R.id.dismiss_button);
+ mIndicationContainer = requireViewById(R.id.indication_container);
+ mIndicationText = mIndicationContainer.findViewById(R.id.indication_text);
bindDefaultActionChips();
@@ -208,6 +215,14 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
}
}
+ void setIndicationText(CharSequence text) {
+ mIndicationText.setText(text);
+
+ // Set the visibility of clipboard indication based on the text is empty or not.
+ int visibility = text.isEmpty() ? View.GONE : View.VISIBLE;
+ mIndicationContainer.setVisibility(visibility);
+ }
+
void setMinimized(boolean minimized) {
if (minimized) {
mMinimizedPreview.setVisibility(View.VISIBLE);
@@ -221,6 +236,18 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
mPreviewBorder.setVisibility(View.VISIBLE);
mActionContainer.setVisibility(View.VISIBLE);
}
+
+ if (showClipboardIndication()) {
+ // Adjust the margin of clipboard indication based on the minimized state.
+ int marginStart = minimized ? getResources().getDimensionPixelSize(
+ R.dimen.overlay_action_container_margin_horizontal)
+ : getResources().getDimensionPixelSize(
+ R.dimen.overlay_action_container_minimum_edge_spacing);
+ ConstraintLayout.LayoutParams params =
+ (ConstraintLayout.LayoutParams) mIndicationContainer.getLayoutParams();
+ params.setMarginStart(marginStart);
+ mIndicationContainer.setLayoutParams(params);
+ }
}
void setInsets(WindowInsets insets, int orientation) {
@@ -313,6 +340,7 @@ public class ClipboardOverlayView extends DraggableConstraintLayout {
setTranslationX(0);
setAlpha(0);
mActionContainerBackground.setVisibility(View.GONE);
+ mIndicationContainer.setVisibility(View.GONE);
mDismissButton.setVisibility(View.GONE);
mShareChip.setVisibility(View.GONE);
mRemoteCopyChip.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
index 527819c73e2f..c81f0d98f3ad 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlaySuppressionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/dagger/ClipboardOverlayOverrideModule.kt
@@ -15,18 +15,26 @@
*/
package com.android.systemui.clipboardoverlay.dagger
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProvider
+import com.android.systemui.clipboardoverlay.ClipboardIndicationProviderImpl
import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionController
import com.android.systemui.clipboardoverlay.ClipboardOverlaySuppressionControllerImpl
import dagger.Binds
import dagger.Module
-/** Dagger Module for code in the clipboard overlay package. */
+/** Dagger Module to provide default implementations which could be overridden. */
@Module
-interface ClipboardOverlaySuppressionModule {
+interface ClipboardOverlayOverrideModule {
/** Provides implementation for [ClipboardOverlaySuppressionController]. */
@Binds
fun provideClipboardOverlaySuppressionController(
clipboardOverlaySuppressionControllerImpl: ClipboardOverlaySuppressionControllerImpl
): ClipboardOverlaySuppressionController
+
+ /** Provides implementation for [ClipboardIndicationProvider]. */
+ @Binds
+ fun provideClipboardIndicationProvider(
+ clipboardIndicationProviderImpl: ClipboardIndicationProviderImpl
+ ): ClipboardIndicationProvider
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index d1c728cd74fa..19238804fb12 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -18,19 +18,12 @@ package com.android.systemui.common.data
import com.android.systemui.common.data.repository.PackageChangeRepository
import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
-import com.android.systemui.common.ui.data.repository.ConfigurationRepository
-import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import dagger.Binds
import dagger.Module
@Module
abstract class CommonDataLayerModule {
@Binds
- abstract fun bindConfigurationRepository(
- impl: ConfigurationRepositoryImpl
- ): ConfigurationRepository
-
- @Binds
abstract fun bindPackageChangeRepository(
impl: PackageChangeRepositoryImpl
): PackageChangeRepository
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
index b36da3bfcd26..a3735f95cf74 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/ConfigurationStateModule.kt
@@ -28,7 +28,7 @@ import javax.inject.Qualifier
/**
* Annotates elements that provide information from the global configuration.
*
- * The global configuration is the one associted with the main display. Secondary displays will
+ * The global configuration is the one associated with the main display. Secondary displays will
* apply override to the global configuration. Elements annotated with this shouldn't be used for
* secondary displays.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 2052c70e740d..df891523798f 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -23,13 +23,17 @@ import android.view.DisplayInfo
import androidx.annotation.DimenRes
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.common.ui.GlobalConfig
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.wrapper.DisplayUtilsWrapper
import dagger.Binds
import dagger.Module
-import javax.inject.Inject
+import dagger.Provides
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -57,66 +61,62 @@ interface ConfigurationRepository {
fun getDimensionPixelSize(id: Int): Int
}
-@SysUISingleton
class ConfigurationRepositoryImpl
-@Inject
+@AssistedInject
constructor(
- private val configurationController: ConfigurationController,
- private val context: Context,
+ @Assisted private val configurationController: ConfigurationController,
+ @Assisted private val context: Context,
@Application private val scope: CoroutineScope,
private val displayUtils: DisplayUtilsWrapper,
) : ConfigurationRepository {
private val displayInfo = MutableStateFlow(DisplayInfo())
- override val onAnyConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onUiModeChanged() {
- sendUpdate("ConfigurationRepository#onUiModeChanged")
- }
-
- override fun onThemeChanged() {
- sendUpdate("ConfigurationRepository#onThemeChanged")
- }
-
- override fun onConfigChanged(newConfig: Configuration) {
- sendUpdate("ConfigurationRepository#onConfigChanged")
- }
-
- fun sendUpdate(reason: String) {
- trySendWithFailureLogging(Unit, reason)
- }
+ override val onAnyConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onUiModeChanged() {
+ sendUpdate("ConfigurationRepository#onUiModeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val onConfigurationChange: Flow<Unit> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
- }
+ override fun onThemeChanged() {
+ sendUpdate("ConfigurationRepository#onThemeChanged")
}
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
- }
- override val configurationValues: Flow<Configuration> =
- conflatedCallbackFlow {
- val callback =
- object : ConfigurationController.ConfigurationListener {
- override fun onConfigChanged(newConfig: Configuration) {
- trySend(newConfig)
- }
- }
-
- trySend(context.resources.configuration)
- configurationController.addCallback(callback)
- awaitClose { configurationController.removeCallback(callback) }
+ override fun onConfigChanged(newConfig: Configuration) {
+ sendUpdate("ConfigurationRepository#onConfigChanged")
+ }
+
+ fun sendUpdate(reason: String) {
+ trySendWithFailureLogging(Unit, reason)
+ }
+ }
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val onConfigurationChange: Flow<Unit> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySendWithFailureLogging(Unit, "ConfigurationRepository#onConfigChanged")
+ }
}
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
+
+ override val configurationValues: Flow<Configuration> = conflatedCallbackFlow {
+ val callback =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration) {
+ trySend(newConfig)
+ }
+ }
+
+ trySend(context.resources.configuration)
+ configurationController.addCallback(callback)
+ awaitClose { configurationController.removeCallback(callback) }
+ }
override val scaleForResolution: StateFlow<Float> =
onConfigurationChange
@@ -134,7 +134,7 @@ constructor(
maxDisplayMode.physicalWidth,
maxDisplayMode.physicalHeight,
displayInfo.value.naturalWidth,
- displayInfo.value.naturalHeight
+ displayInfo.value.naturalHeight,
)
return if (scaleFactor == Float.POSITIVE_INFINITY) 1f else scaleFactor
}
@@ -144,9 +144,40 @@ constructor(
override fun getDimensionPixelSize(@DimenRes id: Int): Int {
return context.resources.getDimensionPixelSize(id)
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ context: Context,
+ configurationController: ConfigurationController,
+ ): ConfigurationRepositoryImpl
+ }
}
@Module
-interface ConfigurationRepositoryModule {
- @Binds fun bindImpl(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+abstract class ConfigurationRepositoryModule {
+
+ /**
+ * For compatibility reasons. Ideally, only the an annotated [ConfigurationRepository] should be
+ * injected.
+ */
+ @Binds
+ @Deprecated("Use the ConfigurationRepository annotated with @GlobalConfig instead.")
+ @SysUISingleton
+ abstract fun provideDefaultConfigRepository(
+ @GlobalConfig configurationRepository: ConfigurationRepository
+ ): ConfigurationRepository
+
+ companion object {
+ @Provides
+ @GlobalConfig
+ @SysUISingleton
+ fun provideGlobalConfigRepository(
+ context: Context,
+ @GlobalConfig configurationController: ConfigurationController,
+ factory: ConfigurationRepositoryImpl.Factory,
+ ): ConfigurationRepository {
+ return factory.create(context, configurationController)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
index adb1ee2b22ee..eb423d69a4eb 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt
@@ -32,6 +32,7 @@ import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.onStart
/** Business logic related to configuration changes. */
+// TODO: b/374267505 - Create a @ShadeDisplayWindow annotated version of this.
@SysUISingleton
class ConfigurationInteractor @Inject constructor(private val repository: ConfigurationRepository) {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
index 012c844586bc..b80e77ce5b2e 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -19,13 +19,13 @@ package com.android.systemui.communal.smartspace
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
-import android.content.Context
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
@@ -42,8 +42,7 @@ import javax.inject.Named
class CommunalSmartspaceController
@Inject
constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Named(LOCKSCREEN_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
@@ -55,6 +54,7 @@ constructor(
private const val TAG = "CommunalSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
@@ -104,7 +104,11 @@ constructor(
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null) {
@@ -119,11 +123,11 @@ constructor(
}
val newSession =
- smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_GLANCEABLE_HUB).build()
)
Log.d(TAG, "Starting smartspace session for communal")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
@@ -163,7 +167,7 @@ constructor(
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -174,7 +178,7 @@ constructor(
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index ca4bbc0ae3bd..00c62092421d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -33,6 +33,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.compose.theme.PlatformTheme
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags.communalEditWidgetsActivityFinishFix
@@ -44,6 +45,7 @@ import com.android.systemui.communal.ui.compose.CommunalHub
import com.android.systemui.communal.ui.view.layout.sections.CommunalAppWidgetSection
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
@@ -52,13 +54,13 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
import kotlinx.coroutines.flow.first
-import com.android.app.tracing.coroutines.launchTraced as launch
/** An Activity for editing the widgets that appear in hub mode. */
class EditWidgetsActivity
@Inject
constructor(
private val communalViewModel: CommunalEditModeViewModel,
+ private val keyguardInteractor: KeyguardInteractor,
private var windowManagerService: IWindowManager? = null,
private val uiEventLogger: UiEventLogger,
private val widgetConfiguratorFactory: WidgetConfigurationController.Factory,
@@ -223,6 +225,9 @@ constructor(
communalViewModel.currentScene.first { it == CommunalScenes.Blank }
}
+ // Wait for dream to exit, if we were previously dreaming.
+ keyguardInteractor.isDreaming.first { !it }
+
communalViewModel.setEditModeState(EditModeState.SHOWING)
// Inform the ActivityController that we are now fully visible.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
index 202edf71fa11..2f686fd91ede 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManager.kt
@@ -19,7 +19,10 @@ package com.android.systemui.communal.widgets
import android.appwidget.AppWidgetHost.AppWidgetHostListener
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
+import android.content.IntentSender
import android.os.IBinder
+import android.os.OutcomeReceiver
+import android.os.RemoteException
import android.os.UserHandle
import android.widget.RemoteViews
import com.android.server.servicewatcher.ServiceWatcher
@@ -27,14 +30,19 @@ import com.android.server.servicewatcher.ServiceWatcher.ServiceListener
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.launch
/**
* Manages updates to Glanceable Hub widgets and requests to edit them from the headless system
@@ -47,6 +55,8 @@ import kotlinx.coroutines.channels.awaitClose
class GlanceableHubWidgetManager
@Inject
constructor(
+ @Background private val bgExecutor: Executor,
+ @Background private val bgScope: CoroutineScope,
glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
@CommunalLog logBuffer: LogBuffer,
serviceWatcherFactory: ServiceWatcherFactory<GlanceableHubWidgetManagerServiceInfo?>,
@@ -101,8 +111,7 @@ constructor(
rank: Int?,
configurator: WidgetConfigurator?,
) = runOnService { service ->
- // TODO(b/375036327): Add support for widget configuration
- service.addWidget(provider, user, rank ?: -1)
+ service.addWidget(provider, user, rank ?: -1, createIConfigureWidgetCallback(configurator))
}
/** Requests the foreground user to delete a widget. */
@@ -129,18 +138,54 @@ constructor(
)
}
- private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
- serviceWatcher.runOnBinder(
- object : ServiceWatcher.BinderOperation {
- override fun run(binder: IBinder?) {
- block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+ /**
+ * Requests the foreground user for the [IntentSender] to start a configuration activity for the
+ * widget.
+ *
+ * @param appWidgetId Id of the widget to configure.
+ * @param outcomeReceiver Callback for receiving the result or error.
+ * @param executor Executor to run the callback on.
+ */
+ fun getIntentSenderForConfigureActivity(
+ appWidgetId: Int,
+ outcomeReceiver: OutcomeReceiver<IntentSender?, Throwable>,
+ executor: Executor,
+ ) {
+ bgExecutor.execute {
+ serviceWatcher.runOnBinder(
+ object : ServiceWatcher.BinderOperation {
+ override fun run(binder: IBinder?) {
+ val service = IGlanceableHubWidgetManagerService.Stub.asInterface(binder)
+ try {
+ val result = service.getIntentSenderForConfigureActivity(appWidgetId)
+ executor.execute { outcomeReceiver.onResult(result) }
+ } catch (e: RemoteException) {
+ executor.execute { outcomeReceiver.onError(e) }
+ }
+ }
+
+ override fun onError(t: Throwable?) {
+ t?.let { executor.execute { outcomeReceiver.onError(t) } }
+ }
}
+ )
+ }
+ }
- override fun onError(t: Throwable?) {
- // TODO(b/375236794): handle failure in case service is unbound
+ private fun runOnService(block: (IGlanceableHubWidgetManagerService) -> Unit) {
+ bgExecutor.execute {
+ serviceWatcher.runOnBinder(
+ object : ServiceWatcher.BinderOperation {
+ override fun run(binder: IBinder?) {
+ block(IGlanceableHubWidgetManagerService.Stub.asInterface(binder))
+ }
+
+ override fun onError(t: Throwable?) {
+ // TODO(b/375236794): handle failure in case service is unbound
+ }
}
- }
- )
+ )
+ }
}
private fun createIAppWidgetHostListener(
@@ -165,6 +210,30 @@ constructor(
}
}
+ private fun createIConfigureWidgetCallback(
+ configurator: WidgetConfigurator?
+ ): IConfigureWidgetCallback? {
+ return configurator?.let {
+ object : IConfigureWidgetCallback.Stub() {
+ override fun onConfigureWidget(
+ appWidgetId: Int,
+ resultReceiver: IConfigureWidgetCallback.IResultReceiver?,
+ ) {
+ bgScope.launch {
+ val success = configurator.configureWidget(appWidgetId)
+ try {
+ resultReceiver?.onResult(success)
+ } catch (e: RemoteException) {
+ logger.e({ "Error reporting widget configuration result: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
companion object {
private const val TAG = "GlanceableHubWidgetManager"
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
index 4d042fc277de..0e43e2aa4c14 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/GlanceableHubWidgetManagerService.kt
@@ -20,8 +20,10 @@ import android.appwidget.AppWidgetHost.AppWidgetHostListener
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Intent
+import android.content.IntentSender
import android.os.IBinder
import android.os.RemoteCallbackList
+import android.os.RemoteException
import android.os.UserHandle
import android.widget.RemoteViews
import androidx.lifecycle.LifecycleService
@@ -29,11 +31,13 @@ import androidx.lifecycle.lifecycleScope
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IAppWidgetHostListener
+import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IConfigureWidgetCallback
import com.android.systemui.communal.widgets.IGlanceableHubWidgetManagerService.IGlanceableHubWidgetsListener
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -98,7 +102,15 @@ constructor(
val job =
widgetRepository.communalWidgets
- .onEach { widgets -> listener.onWidgetsUpdated(widgets) }
+ .onEach { widgets ->
+ try {
+ listener.onWidgetsUpdated(widgets)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing widget update: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
+ }
.launchIn(lifecycleScope)
widgetListenersRegistry.register(listener, job)
}
@@ -122,7 +134,12 @@ constructor(
appWidgetHost.setListener(appWidgetId, createListener(listener))
}
- private fun addWidgetInternal(provider: ComponentName?, user: UserHandle?, rank: Int) {
+ private fun addWidgetInternal(
+ provider: ComponentName?,
+ user: UserHandle?,
+ rank: Int,
+ callback: IConfigureWidgetCallback?,
+ ) {
if (provider == null) {
throw IllegalStateException("Provider cannot be null")
}
@@ -131,8 +148,29 @@ constructor(
throw IllegalStateException("User cannot be null")
}
- // TODO(b/375036327): Add support for widget configuration
- widgetRepository.addWidget(provider, user, rank, configurator = null)
+ val configurator =
+ callback?.let {
+ WidgetConfigurator { appWidgetId ->
+ try {
+ val result = CompletableDeferred<Boolean>()
+ val resultReceiver =
+ object : IConfigureWidgetCallback.IResultReceiver.Stub() {
+ override fun onResult(success: Boolean) {
+ result.complete(success)
+ }
+ }
+
+ callback.onConfigureWidget(appWidgetId, resultReceiver)
+ result.await()
+ } catch (e: RemoteException) {
+ logger.e({ "Error configuring widget: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ false
+ }
+ }
+ }
+ widgetRepository.addWidget(provider, user, rank, configurator)
}
private fun deleteWidgetInternal(appWidgetId: Int) {
@@ -168,22 +206,55 @@ constructor(
widgetRepository.resizeWidget(appWidgetId, spanY, appWidgetIds.zip(ranks).toMap())
}
+ private fun getIntentSenderForConfigureActivityInternal(appWidgetId: Int): IntentSender? {
+ return try {
+ appWidgetHost.getIntentSenderForConfigureActivity(appWidgetId, /* intentFlags= */ 0)
+ } catch (e: IntentSender.SendIntentException) {
+ logger.e({ "Error getting intent sender for configure activity" }) {
+ str1 = e.localizedMessage
+ }
+ null
+ }
+ }
+
private fun createListener(listener: IAppWidgetHostListener): AppWidgetHostListener {
return object : AppWidgetHostListener {
override fun onUpdateProviderInfo(appWidget: AppWidgetProviderInfo?) {
- listener.onUpdateProviderInfo(appWidget)
+ try {
+ listener.onUpdateProviderInfo(appWidget)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on update provider info: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun updateAppWidget(views: RemoteViews?) {
- listener.updateAppWidget(views)
+ try {
+ listener.updateAppWidget(views)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget: $str1" }) { str1 = e.localizedMessage }
+ }
}
override fun updateAppWidgetDeferred(packageName: String?, appWidgetId: Int) {
- listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ try {
+ listener.updateAppWidgetDeferred(packageName, appWidgetId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error updating app widget deferred: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
override fun onViewDataChanged(viewId: Int) {
- listener.onViewDataChanged(viewId)
+ try {
+ listener.onViewDataChanged(viewId)
+ } catch (e: RemoteException) {
+ logger.e({ "Error pushing on view data changed: $str1" }) {
+ str1 = e.localizedMessage
+ }
+ }
}
}
}
@@ -219,11 +290,16 @@ constructor(
}
}
- override fun addWidget(provider: ComponentName?, user: UserHandle?, rank: Int) {
+ override fun addWidget(
+ provider: ComponentName?,
+ user: UserHandle?,
+ rank: Int,
+ callback: IConfigureWidgetCallback?,
+ ) {
val iden = clearCallingIdentity()
try {
- addWidgetInternal(provider, user, rank)
+ addWidgetInternal(provider, user, rank, callback)
} finally {
restoreCallingIdentity(iden)
}
@@ -263,6 +339,16 @@ constructor(
restoreCallingIdentity(iden)
}
}
+
+ override fun getIntentSenderForConfigureActivity(appWidgetId: Int): IntentSender? {
+ val iden = clearCallingIdentity()
+
+ try {
+ return getIntentSenderForConfigureActivityInternal(appWidgetId)
+ } finally {
+ restoreCallingIdentity(iden)
+ }
+ }
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
index e556472c78a7..d71b2303db30 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/IGlanceableHubWidgetManagerService.aidl
@@ -2,6 +2,7 @@ package com.android.systemui.communal.widgets;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
+import android.content.IntentSender;
import android.os.UserHandle;
import android.widget.RemoteViews;
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel;
@@ -21,7 +22,8 @@ interface IGlanceableHubWidgetManagerService {
oneway void setAppWidgetHostListener(int appWidgetId, in IAppWidgetHostListener listener);
// Requests to add a widget in the Glanceable Hub.
- oneway void addWidget(in ComponentName provider, in UserHandle user, int rank);
+ oneway void addWidget(in ComponentName provider, in UserHandle user, int rank,
+ in IConfigureWidgetCallback callback);
// Requests to delete a widget from the Glanceable Hub.
oneway void deleteWidget(int appWidgetId);
@@ -32,6 +34,9 @@ interface IGlanceableHubWidgetManagerService {
// Requests to resize a widget in the Glanceable Hub.
oneway void resizeWidget(int appWidgetId, int spanY, in int[] appWidgetIds, in int[] ranks);
+ // Returns the [IntentSender] for launching the configuration activity of the given widget.
+ IntentSender getIntentSenderForConfigureActivity(int appWidgetId);
+
// Listener for Glanceable Hub widget updates
oneway interface IGlanceableHubWidgetsListener {
// Called when widgets have updated.
@@ -48,4 +53,15 @@ interface IGlanceableHubWidgetManagerService {
void onViewDataChanged(int viewId);
}
+
+ oneway interface IConfigureWidgetCallback {
+ // Called when the given widget should launch its configuration activity. The caller should
+ // report the result through the [IResultReceiver].
+ void onConfigureWidget(int appWidgetId, in IResultReceiver resultReceiver);
+
+ interface IResultReceiver {
+ // Called when the widget configuration operation returns a result.
+ void onResult(boolean success);
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
index d157cd7acb76..fddec5659b96 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetConfigurationController.kt
@@ -18,16 +18,19 @@ package com.android.systemui.communal.widgets
import android.app.Activity
import android.app.ActivityOptions
-import android.content.ActivityNotFoundException
+import android.content.IntentSender
+import android.os.OutcomeReceiver
import android.window.SplashScreen
import androidx.activity.ComponentActivity
import com.android.systemui.communal.shared.model.GlanceableHubMultiUserHelper
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.nullableAtomicReference
import dagger.Lazy
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -43,12 +46,22 @@ constructor(
private val appWidgetHostLazy: Lazy<CommunalAppWidgetHost>,
@Background private val bgDispatcher: CoroutineDispatcher,
private val glanceableHubMultiUserHelper: GlanceableHubMultiUserHelper,
-) : WidgetConfigurator {
+ private val glanceableHubWidgetManagerLazy: Lazy<GlanceableHubWidgetManager>,
+ @Main private val mainExecutor: Executor,
+) : WidgetConfigurator, OutcomeReceiver<IntentSender?, Throwable> {
@AssistedFactory
fun interface Factory {
fun create(activity: ComponentActivity): WidgetConfigurationController
}
+ private val activityOptions: ActivityOptions
+ get() =
+ ActivityOptions.makeBasic().apply {
+ pendingIntentBackgroundActivityStartMode =
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
+ splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
+ }
+
private var result: CompletableDeferred<Boolean>? by nullableAtomicReference()
override suspend fun configureWidget(appWidgetId: Int): Boolean =
@@ -57,37 +70,64 @@ constructor(
throw IllegalStateException("There is already a pending configuration")
}
result = CompletableDeferred()
- val options =
- ActivityOptions.makeBasic().apply {
- pendingIntentBackgroundActivityStartMode =
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED
- splashScreenStyle = SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR
- }
try {
- // TODO(b/375036327): Add support for widget configuration
if (
!glanceableHubMultiUserHelper.glanceableHubHsumFlagEnabled ||
!glanceableHubMultiUserHelper.isInHeadlessSystemUser()
) {
+ // Start configuration activity directly if we're running in a foreground user
with(appWidgetHostLazy.get()) {
startAppWidgetConfigureActivityForResult(
activity,
appWidgetId,
0,
REQUEST_CODE,
- options.toBundle(),
+ activityOptions.toBundle(),
+ )
+ }
+ } else {
+ with(glanceableHubWidgetManagerLazy.get()) {
+ // Use service to get intent sender and start configuration activity
+ // locally if running in a headless system user
+ getIntentSenderForConfigureActivity(
+ appWidgetId,
+ outcomeReceiver = this@WidgetConfigurationController,
+ mainExecutor,
)
}
}
- } catch (e: ActivityNotFoundException) {
+ } catch (_: Exception) {
setConfigurationResult(Activity.RESULT_CANCELED)
}
- val value = result?.await() ?: false
+ val value = result?.await() == true
result = null
return@withContext value
}
+ // Called when an intent sender is returned, and the configuration activity should be started.
+ override fun onResult(intentSender: IntentSender?) {
+ if (intentSender == null) {
+ setConfigurationResult(Activity.RESULT_CANCELED)
+ return
+ }
+
+ activity.startIntentSenderForResult(
+ intentSender,
+ REQUEST_CODE,
+ /* fillInIntent = */ null,
+ /* flagsMask = */ 0,
+ /* flagsValues = */ 0,
+ /* extraFlags = */ 0,
+ activityOptions.toBundle(),
+ )
+ }
+
+ // Called when there is an error getting the intent sender.
+ override fun onError(e: Throwable) {
+ setConfigurationResult(Activity.RESULT_CANCELED)
+ }
+
fun setConfigurationResult(resultCode: Int) {
result?.complete(resultCode == Activity.RESULT_OK)
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 609b7330b600..2e323d40edcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -29,7 +29,7 @@ import com.android.systemui.accessibility.AccessibilityModule;
import com.android.systemui.accessibility.SystemActionsModule;
import com.android.systemui.accessibility.data.repository.AccessibilityRepositoryModule;
import com.android.systemui.battery.BatterySaverModule;
-import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlaySuppressionModule;
+import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayOverrideModule;
import com.android.systemui.display.ui.viewmodel.ConnectingDisplayViewModel;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
@@ -125,7 +125,7 @@ import javax.inject.Named;
AospPolicyModule.class,
BatterySaverModule.class,
CentralSurfacesModule.class,
- ClipboardOverlaySuppressionModule.class,
+ ClipboardOverlayOverrideModule.class,
CollapsedStatusBarFragmentStartableModule.class,
ConnectingDisplayViewModel.StartableModule.class,
DefaultBlueprintModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index cb649f28f95b..9138243c642c 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -49,6 +49,7 @@ import com.android.systemui.classifier.FalsingModule;
import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule;
import com.android.systemui.common.data.CommonDataLayerModule;
import com.android.systemui.common.ui.ConfigurationStateModule;
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryModule;
import com.android.systemui.common.usagestats.data.CommonUsageStatsDataLayerModule;
import com.android.systemui.communal.dagger.CommunalModule;
import com.android.systemui.complication.dagger.ComplicationComponent;
@@ -212,6 +213,7 @@ import javax.inject.Named;
CommunalModule.class,
CommonDataLayerModule.class,
ConfigurationStateModule.class,
+ ConfigurationRepositoryModule.class,
CommonUsageStatsDataLayerModule.class,
ConfigurationControllerModule.class,
ConnectivityModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
index 2bcfea8c1179..45a59015c1b7 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/service/HomeControlsRemoteProxy.kt
@@ -16,20 +16,16 @@
package com.android.systemui.dreams.homecontrols.service
-import android.content.ComponentName
-import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dreams.homecontrols.shared.IHomeControlsRemoteProxy
-import com.android.systemui.dreams.homecontrols.shared.IOnControlsSettingsChangeListener
+import com.android.systemui.dreams.homecontrols.shared.controlsSettings
import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
import com.android.systemui.dump.DumpManager
import com.android.systemui.util.kotlin.FlowDumperImpl
-import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -45,30 +41,8 @@ constructor(
@Assisted private val proxy: IHomeControlsRemoteProxy,
) : FlowDumperImpl(dumpManager) {
- private companion object {
- const val TAG = "HomeControlsRemoteProxy"
- }
-
val componentInfo: Flow<HomeControlsComponentInfo> =
- conflatedCallbackFlow {
- val listener =
- object : IOnControlsSettingsChangeListener.Stub() {
- override fun onControlsSettingsChanged(
- panelComponent: ComponentName?,
- allowTrivialControlsOnLockscreen: Boolean,
- ) {
- trySendWithFailureLogging(
- HomeControlsComponentInfo(
- panelComponent,
- allowTrivialControlsOnLockscreen,
- ),
- TAG,
- )
- }
- }
- proxy.registerListenerForCurrentUser(listener)
- awaitClose { proxy.unregisterListenerForCurrentUser(listener) }
- }
+ proxy.controlsSettings
.distinctUntilChanged()
.stateIn(bgScope, SharingStarted.WhileSubscribed(), null)
.dumpValue("componentInfo")
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
new file mode 100644
index 000000000000..2993ab5a464e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/shared/IHomeControlsRemoteProxyExt.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.dreams.homecontrols.shared
+
+import android.content.ComponentName
+import com.android.systemui.dreams.homecontrols.shared.model.HomeControlsComponentInfo
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+val IHomeControlsRemoteProxy.controlsSettings: Flow<HomeControlsComponentInfo>
+ get() = conflatedCallbackFlow {
+ val listener =
+ object : IOnControlsSettingsChangeListener.Stub() {
+ override fun onControlsSettingsChanged(
+ panelComponent: ComponentName?,
+ allowTrivialControlsOnLockscreen: Boolean,
+ ) {
+ trySend(
+ HomeControlsComponentInfo(panelComponent, allowTrivialControlsOnLockscreen)
+ )
+ }
+ }
+ registerListenerForCurrentUser(listener)
+ awaitClose { unregisterListenerForCurrentUser(listener) }
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
index a65d216aa5a2..6d1fd4df2852 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/system/HomeControlsRemoteService.kt
@@ -57,6 +57,11 @@ constructor(binderFactory: HomeControlsRemoteServiceBinder.Factory) : LifecycleS
super.onBind(intent)
return binder
}
+
+ override fun onDestroy() {
+ super.onDestroy()
+ binder.onDestroy()
+ }
}
class HomeControlsRemoteServiceBinder
@@ -148,6 +153,14 @@ constructor(
}
}
+ fun onDestroy() {
+ logger.d("Service destroyed")
+ callbacks.kill()
+ callbackCount.set(0)
+ collectionJob?.cancel()
+ collectionJob = null
+ }
+
@AssistedFactory
interface Factory {
fun create(lifecycleOwner: LifecycleOwner): HomeControlsRemoteServiceBinder
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 4a8c040e33c1..fd913893b3ac 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -20,7 +20,6 @@ import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
import android.app.smartspace.SmartspaceTarget
-import android.content.Context
import android.graphics.Color
import android.util.Log
import android.view.View
@@ -31,6 +30,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
+import com.android.systemui.settings.UserTracker
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
@@ -44,13 +44,12 @@ import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Named
-/**
- * Controller for managing the smartspace view on the dream
- */
+/** Controller for managing the smartspace view on the dream */
@SysUISingleton
-class DreamSmartspaceController @Inject constructor(
- private val context: Context,
- private val smartspaceManager: SmartspaceManager?,
+class DreamSmartspaceController
+@Inject
+constructor(
+ private val userTracker: UserTracker,
private val execution: Execution,
@Main private val uiExecutor: Executor,
private val smartspaceViewComponentFactory: SmartspaceViewComponent.Factory,
@@ -65,6 +64,7 @@ class DreamSmartspaceController @Inject constructor(
private const val TAG = "DreamSmartspaceCtrlr"
}
+ private var userSmartspaceManager: SmartspaceManager? = null
private var session: SmartspaceSession? = null
private val weatherPlugin: BcSmartspaceDataPlugin? = optionalWeatherPlugin.orElse(null)
private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
@@ -78,66 +78,68 @@ class DreamSmartspaceController @Inject constructor(
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
- var preconditionListener = object : SmartspacePrecondition.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var preconditionListener =
+ object : SmartspacePrecondition.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
precondition.addListener(preconditionListener)
}
- var filterListener = object : SmartspaceTargetFilter.Listener {
- override fun onCriteriaChanged() {
- reloadSmartspace()
+ var filterListener =
+ object : SmartspaceTargetFilter.Listener {
+ override fun onCriteriaChanged() {
+ reloadSmartspace()
+ }
}
- }
init {
targetFilter?.addListener(filterListener)
}
- var stateChangeListener = object : View.OnAttachStateChangeListener {
- override fun onViewAttachedToWindow(v: View) {
- val view = v as SmartspaceView
- // Until there is dream color matching
- view.setPrimaryTextColor(Color.WHITE)
- smartspaceViews.add(view)
- connectSession()
- view.setDozeAmount(0f)
- }
+ var stateChangeListener =
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {
+ val view = v as SmartspaceView
+ // Until there is dream color matching
+ view.setPrimaryTextColor(Color.WHITE)
+ smartspaceViews.add(view)
+ connectSession()
+ view.setDozeAmount(0f)
+ }
- override fun onViewDetachedFromWindow(v: View) {
- smartspaceViews.remove(v as SmartspaceView)
+ override fun onViewDetachedFromWindow(v: View) {
+ smartspaceViews.remove(v as SmartspaceView)
- if (smartspaceViews.isEmpty()) {
- disconnect()
+ if (smartspaceViews.isEmpty()) {
+ disconnect()
+ }
}
}
- }
- private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
- execution.assertIsMainThread()
+ private val sessionListener =
+ SmartspaceSession.OnTargetsAvailableListener { targets ->
+ execution.assertIsMainThread()
- // The weather data plugin takes unfiltered targets and performs the filtering internally.
- weatherPlugin?.onTargetsAvailable(targets)
+ // The weather data plugin takes unfiltered targets and performs the filtering
+ // internally.
+ weatherPlugin?.onTargetsAvailable(targets)
- onTargetsAvailableUnfiltered(targets)
- val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
- plugin?.onTargetsAvailable(filteredTargets)
- }
+ onTargetsAvailableUnfiltered(targets)
+ val filteredTargets =
+ targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+ plugin?.onTargetsAvailable(filteredTargets)
+ }
- /**
- * Constructs the weather view with custom layout and connects it to the weather plugin.
- */
+ /** Constructs the weather view with custom layout and connects it to the weather plugin. */
fun buildAndConnectWeatherView(parent: ViewGroup, customView: View?): View? {
return buildAndConnectViewWithPlugin(parent, weatherPlugin, customView)
}
- /**
- * Constructs the smartspace view and connects it to the smartspace service.
- */
+ /** Constructs the smartspace view and connects it to the smartspace service. */
fun buildAndConnectView(parent: ViewGroup): View? {
return buildAndConnectViewWithPlugin(parent, plugin, null)
}
@@ -145,7 +147,7 @@ class DreamSmartspaceController @Inject constructor(
private fun buildAndConnectViewWithPlugin(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
execution.assertIsMainThread()
@@ -163,12 +165,13 @@ class DreamSmartspaceController @Inject constructor(
private fun buildView(
parent: ViewGroup,
smartspaceDataPlugin: BcSmartspaceDataPlugin?,
- customView: View?
+ customView: View?,
): View? {
return if (smartspaceDataPlugin != null) {
- val view = smartspaceViewComponentFactory.create(parent, smartspaceDataPlugin,
- stateChangeListener, customView)
- .getView()
+ val view =
+ smartspaceViewComponentFactory
+ .create(parent, smartspaceDataPlugin, stateChangeListener, customView)
+ .getView()
if (view !is View) {
return null
}
@@ -179,12 +182,17 @@ class DreamSmartspaceController @Inject constructor(
}
private fun hasActiveSessionListeners(): Boolean {
- return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+ return smartspaceViews.isNotEmpty() ||
+ listeners.isNotEmpty() ||
unfilteredListeners.isNotEmpty()
}
private fun connectSession() {
- if (smartspaceManager == null) {
+ if (userSmartspaceManager == null) {
+ userSmartspaceManager =
+ userTracker.userContext.getSystemService(SmartspaceManager::class.java)
+ }
+ if (userSmartspaceManager == null) {
return
}
if (plugin == null && weatherPlugin == null) {
@@ -198,25 +206,21 @@ class DreamSmartspaceController @Inject constructor(
return
}
- val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, UI_SURFACE_DREAM).build()
- )
+ val newSession =
+ userSmartspaceManager?.createSmartspaceSession(
+ SmartspaceConfig.Builder(userTracker.userContext, UI_SURFACE_DREAM).build()
+ )
Log.d(TAG, "Starting smartspace session for dream")
- newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+ newSession?.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
weatherPlugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
- plugin?.registerSmartspaceEventNotifier {
- e ->
- session?.notifySmartspaceEvent(e)
- }
+ plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
reloadSmartspace()
}
- /**
- * Disconnects the smartspace view from the smartspace service and cleans up any resources.
- */
+ /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
private fun disconnect() {
if (hasActiveSessionListeners()) return
@@ -259,7 +263,7 @@ class DreamSmartspaceController @Inject constructor(
private fun addAndRegisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.registerListener(listener)
@@ -270,7 +274,7 @@ class DreamSmartspaceController @Inject constructor(
private fun removeAndUnregisterListener(
listener: SmartspaceTargetListener,
- smartspaceDataPlugin: BcSmartspaceDataPlugin?
+ smartspaceDataPlugin: BcSmartspaceDataPlugin?,
) {
execution.assertIsMainThread()
smartspaceDataPlugin?.unregisterListener(listener)
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 349236551ecf..b2fcc434630c 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -45,7 +45,7 @@ open class DumpManager @Inject constructor() {
/** See [registerCriticalDumpable]. */
fun registerCriticalDumpable(module: Dumpable) {
- registerCriticalDumpable(module::class.java.canonicalName, module)
+ registerCriticalDumpable(module::class.java.name, module)
}
/**
@@ -62,7 +62,7 @@ open class DumpManager @Inject constructor() {
/** See [registerNormalDumpable]. */
fun registerNormalDumpable(module: Dumpable) {
- registerNormalDumpable(module::class.java.canonicalName, module)
+ registerNormalDumpable(module::class.java.name, module)
}
/**
@@ -104,13 +104,10 @@ open class DumpManager @Inject constructor() {
dumpables[name] = DumpableEntry(module, name, priority)
}
- /**
- * Same as the above override, but automatically uses the canonical class name as the dumpable
- * name.
- */
+ /** Same as the above override, but automatically uses the class name as the dumpable name. */
@Synchronized
fun registerDumpable(module: Dumpable) {
- registerDumpable(module::class.java.canonicalName, module)
+ registerDumpable(module::class.java.name, module)
}
/** Unregisters a previously-registered dumpable. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
index 303f91688575..911331b8bff1 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java
@@ -42,7 +42,7 @@ import androidx.annotation.Nullable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserTracker;
import com.android.systemui.util.settings.GlobalSettings;
import java.io.PrintWriter;
@@ -51,6 +51,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
@@ -74,7 +75,6 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
static final String TAG = "SysUIFlags";
private final FlagManager mFlagManager;
- private final Context mContext;
private final GlobalSettings mGlobalSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
@@ -84,6 +84,8 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
private final Map<String, String> mStringFlagCache = new ConcurrentHashMap<>();
private final Map<String, Integer> mIntFlagCache = new ConcurrentHashMap<>();
private final Restarter mRestarter;
+ private final UserTracker mUserTracker;
+ private final Executor mMainExecutor;
private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
new ServerFlagReader.ChangeListener() {
@@ -118,6 +120,18 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
}
};
+ private final UserTracker.Callback mUserChangedCallback =
+ new UserTracker.Callback() {
+ @Override
+ public void onUserChanged(int newUser, @NonNull Context userContext) {
+ mContext.unregisterReceiver(mReceiver);
+ mContext = userContext;
+ registerReceiver();
+ }
+ };
+
+ private Context mContext;
+
@Inject
public FeatureFlagsClassicDebug(
FlagManager flagManager,
@@ -128,10 +142,11 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
ServerFlagReader serverFlagReader,
@Named(ALL_FLAGS) Map<String, Flag<?>> allFlags,
Restarter restarter,
- UserContextProvider userContextProvider) {
+ UserTracker userTracker,
+ @Main Executor executor) {
mFlagManager = flagManager;
if (classicFlagsMultiUser()) {
- mContext = userContextProvider.createCurrentUserContext(context);
+ mContext = userTracker.createCurrentUserContext(context);
} else {
mContext = context;
}
@@ -141,19 +156,28 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic {
mServerFlagReader = serverFlagReader;
mAllFlags = allFlags;
mRestarter = restarter;
+ mUserTracker = userTracker;
+ mMainExecutor = executor;
}
/** Call after construction to setup listeners. */
void init() {
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_SET_FLAG);
- filter.addAction(ACTION_GET_FLAGS);
mFlagManager.setOnSettingsChangedAction(
suppressRestart -> restartSystemUI(suppressRestart, "Settings changed"));
mFlagManager.setClearCacheAction(this::removeFromCache);
+ registerReceiver();
+ if (classicFlagsMultiUser()) {
+ mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ }
+ mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
+ }
+
+ void registerReceiver() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_SET_FLAG);
+ filter.addAction(ACTION_GET_FLAGS);
mContext.registerReceiver(mReceiver, filter, null, null,
Context.RECEIVER_EXPORTED_UNAUDITED);
- mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
index 89cdd25181cb..585f7edeb0f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -33,13 +33,13 @@ import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
-import javax.inject.Inject
interface StickyKeysRepository {
val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
@@ -71,7 +71,7 @@ constructor(
override val settingEnabled: Flow<Boolean> =
secureSettingsRepository
- .boolSettingForActiveUser(SETTING_KEY, defaultValue = false)
+ .boolSetting(SETTING_KEY, defaultValue = false)
.onEach { stickyKeysLogger.logNewSettingValue(it) }
.flowOn(backgroundDispatcher)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
index 032af94e62aa..2914cb9fdfdc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -44,7 +44,7 @@ constructor(
private val keyguardStateController: KeyguardStateController,
private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
private val keyguardDismissTransitionInteractor: KeyguardDismissTransitionInteractor,
- private val keyguardTransitions: KeyguardTransitions
+ private val keyguardTransitions: KeyguardTransitions,
) {
/**
@@ -108,27 +108,28 @@ constructor(
* Manager to effect the change.
*/
fun setSurfaceBehindVisibility(visible: Boolean) {
- if (isKeyguardGoingAway == visible) {
- Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+ if (isKeyguardGoingAway && visible) {
+ Log.d(TAG, "#setSurfaceBehindVisibility: already visible, ignoring")
return
}
// The surface behind is always visible if the lockscreen is not showing, so we're already
// visible.
if (visible && isLockscreenShowing != true) {
- Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+ Log.d(TAG, "#setSurfaceBehindVisibility: ignoring since the lockscreen isn't showing")
return
}
-
-
if (visible) {
if (enableNewKeyguardShellTransitions) {
- keyguardTransitions.startKeyguardTransition(false /* keyguardShowing */, false /* aodShowing */)
+ keyguardTransitions.startKeyguardTransition(
+ false /* keyguardShowing */,
+ false, /* aodShowing */
+ )
isKeyguardGoingAway = true
return
}
- // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+ // Make the surface behind the keyguard visible by calling keyguardGoingAway. The
// lockscreen is still showing as well, allowing us to animate unlocked.
Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
activityTaskManagerService.keyguardGoingAway(0)
@@ -153,7 +154,7 @@ constructor(
apps: Array<RemoteAnimationTarget>,
wallpapers: Array<RemoteAnimationTarget>,
nonApps: Array<RemoteAnimationTarget>,
- finishedCallback: IRemoteAnimationFinishedCallback
+ finishedCallback: IRemoteAnimationFinishedCallback,
) {
// Ensure that we've started a dismiss keyguard transition. WindowManager can start the
// going away animation on its own, if an activity launches and then requests dismissing the
@@ -203,27 +204,25 @@ constructor(
*/
private fun setWmLockscreenState(
lockscreenShowing: Boolean? = this.isLockscreenShowing,
- aodVisible: Boolean = this.isAodVisible
+ aodVisible: Boolean = this.isAodVisible,
) {
- Log.d(
- TAG,
- "#setWmLockscreenState(" +
- "isLockscreenShowing=$lockscreenShowing, " +
- "aodVisible=$aodVisible)."
- )
-
if (lockscreenShowing == null) {
Log.d(
TAG,
"isAodVisible=$aodVisible, but lockscreenShowing=null. Waiting for" +
"non-null lockscreenShowing before calling ATMS#setLockScreenShown, which" +
- "will happen once KeyguardTransitionBootInteractor starts the boot transition."
+ "will happen once KeyguardTransitionBootInteractor starts the boot transition.",
)
this.isAodVisible = aodVisible
return
}
if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+ Log.d(
+ TAG,
+ "#setWmLockscreenState: lockscreenShowing=$lockscreenShowing and " +
+ "isAodVisible=$aodVisible were both unchanged, not forwarding to ATMS.",
+ )
return
}
@@ -231,7 +230,7 @@ constructor(
TAG,
"ATMS#setLockScreenShown(" +
"isLockscreenShowing=$lockscreenShowing, " +
- "aodVisible=$aodVisible)."
+ "aodVisible=$aodVisible).",
)
if (enableNewKeyguardShellTransitions) {
keyguardTransitions.startKeyguardTransition(lockscreenShowing, aodVisible)
@@ -247,7 +246,7 @@ constructor(
Log.d(
TAG,
"#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
- "Short-circuiting."
+ "Short-circuiting.",
)
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 6ac0a3f8443f..021cce6d1e23 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -20,6 +20,7 @@ import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.app.animation.Interpolators
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Flags.communalSceneKtfRefactor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -41,7 +42,6 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class FromDozingTransitionInteractor
@@ -135,11 +135,22 @@ constructor(
if (!deviceEntryInteractor.isLockscreenEnabled()) {
if (!SceneContainerFlag.isEnabled) {
- startTransitionTo(KeyguardState.GONE)
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason = "lockscreen not enabled",
+ )
}
} else if (canDismissLockscreen() || isKeyguardGoingAway) {
if (!SceneContainerFlag.isEnabled) {
- startTransitionTo(KeyguardState.GONE)
+ startTransitionTo(
+ KeyguardState.GONE,
+ ownerReason =
+ if (canDismissLockscreen()) {
+ "canDismissLockscreen()"
+ } else {
+ "isKeyguardGoingAway"
+ },
+ )
}
} else if (primaryBouncerShowing) {
if (!SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
index 0dae17c594c8..cd62d5f3b6e1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -16,9 +16,11 @@
package com.android.systemui.keyguard.domain.interactor
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.keyguard.logging.KeyguardLogger
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.log.core.LogLevel.VERBOSE
@@ -29,7 +31,6 @@ import com.android.systemui.statusbar.notification.stack.ui.viewmodel.SharedNoti
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.debounce
-import com.android.app.tracing.coroutines.launchTraced as launch
private val TAG = KeyguardTransitionAuditLogger::class.simpleName!!
@@ -48,6 +49,7 @@ constructor(
private val aodBurnInViewModel: AodBurnInViewModel,
private val shadeInteractor: ShadeInteractor,
private val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val deviceEntryInteractor: DeviceEntryInteractor,
) {
fun start() {
@@ -84,6 +86,18 @@ constructor(
}
scope.launch {
+ deviceEntryInteractor.isUnlocked.collect {
+ logger.log(TAG, VERBOSE, "DeviceEntry isUnlocked", it)
+ }
+ }
+
+ scope.launch {
+ deviceEntryInteractor.isLockscreenEnabled.collect {
+ logger.log(TAG, VERBOSE, "DeviceEntry isLockscreenEnabled", it)
+ }
+ }
+
+ scope.launch {
keyguardInteractor.primaryBouncerShowing.collect {
logger.log(TAG, VERBOSE, "Primary bouncer showing", it)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index abd7f90bbf22..7d4d377c768a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
@@ -33,7 +34,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* Each TransitionInteractor is responsible for determining under which conditions to notify
@@ -201,9 +201,18 @@ sealed class TransitionInteractor(
scope.launch {
keyguardInteractor.onCameraLaunchDetected.filterRelevantKeyguardState().collect {
if (!maybeHandleInsecurePowerGesture()) {
+ val lastStep = transitionInteractor.transitionState.value
+ val modeOnCanceled =
+ if (lastStep.to == KeyguardState.AOD) {
+ // Enabled smooth transition when double-tap camera cancels
+ // transition to AOD
+ TransitionModeOnCanceled.REVERSE
+ } else {
+ TransitionModeOnCanceled.RESET
+ }
startTransitionTo(
toState = KeyguardState.OCCLUDED,
- modeOnCanceled = TransitionModeOnCanceled.RESET,
+ modeOnCanceled = modeOnCanceled,
ownerReason = "keyguardInteractor.onCameraLaunchDetected",
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
index a1f606740cd9..f473a82138e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -18,7 +18,8 @@
package com.android.systemui.keyguard.domain.interactor
-import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.ObservableTransitionState.Idle
+import com.android.compose.animation.scene.ObservableTransitionState.Transition
import com.android.systemui.Flags.transitionRaceCondition
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
@@ -30,6 +31,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.Companion.device
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
+import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.statusbar.notification.domain.interactor.NotificationLaunchAnimationInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
@@ -110,11 +112,8 @@ constructor(
}
.distinctUntilChanged()
- private val isDeviceEntered: Flow<Boolean> by lazy {
- deviceEntryInteractor.get().isDeviceEntered
- }
-
- private val isDeviceNotEntered: Flow<Boolean> by lazy { isDeviceEntered.map { !it } }
+ private val isDeviceEntered by lazy { deviceEntryInteractor.get().isDeviceEntered }
+ private val isDeviceNotEntered by lazy { isDeviceEntered.map { !it } }
/**
* Surface visibility, which is either determined by the default visibility when not
@@ -124,32 +123,17 @@ constructor(
@OptIn(ExperimentalCoroutinesApi::class)
val surfaceBehindVisibility: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- sceneInteractor.get().transitionState.flatMapLatestConflated { transitionState ->
- when (transitionState) {
- is ObservableTransitionState.Transition ->
- when {
- transitionState.fromContent == Scenes.Lockscreen &&
- transitionState.toContent == Scenes.Gone ->
- sceneInteractor
- .get()
- .isTransitionUserInputOngoing
- .flatMapLatestConflated { isUserInputOngoing ->
- if (isUserInputOngoing) {
- isDeviceEntered
- } else {
- flowOf(true)
- }
- }
- transitionState.fromContent == Scenes.Bouncer &&
- transitionState.toContent == Scenes.Gone ->
- transitionState.progress.map { progress ->
- progress >
- FromPrimaryBouncerTransitionInteractor
- .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
- }
- else -> isDeviceEntered
+ sceneInteractor.get().transitionState.flatMapLatestConflated { state ->
+ when {
+ state.isTransitioning(from = Scenes.Lockscreen, to = Scenes.Gone) ->
+ isDeviceEntered
+ state.isTransitioning(from = Scenes.Bouncer, to = Scenes.Gone) ->
+ (state as Transition).progress.map { progress ->
+ progress >
+ FromPrimaryBouncerTransitionInteractor
+ .TO_GONE_SURFACE_BEHIND_VISIBLE_THRESHOLD
}
- is ObservableTransitionState.Idle -> isDeviceEntered
+ else -> lockscreenVisibilityWithScenes.map { !it }
}
}
} else {
@@ -219,6 +203,123 @@ constructor(
}
/**
+ * Scenes that are part of the keyguard and are shown when the device is locked or when the
+ * keyguard still needs to be dismissed.
+ */
+ private val keyguardScenes = setOf(Scenes.Lockscreen, Scenes.Bouncer, Scenes.Communal)
+
+ /**
+ * Scenes that don't belong in the keyguard family and cannot show when the device is locked or
+ * when the keyguard still needs to be dismissed.
+ */
+ private val nonKeyguardScenes = setOf(Scenes.Gone)
+
+ /**
+ * Scenes that can show regardless of device lock or keyguard dismissal states. Other sources of
+ * state need to be consulted to know whether the device has been entered or not.
+ */
+ private val keyguardAgnosticScenes =
+ setOf(
+ Scenes.Shade,
+ Scenes.QuickSettings,
+ Overlays.NotificationsShade,
+ Overlays.QuickSettingsShade,
+ )
+
+ private val lockscreenVisibilityWithScenes =
+ combine(
+ sceneInteractor.get().transitionState.flatMapLatestConflated {
+ when (it) {
+ is Idle -> {
+ when (it.currentScene) {
+ in keyguardScenes -> flowOf(true)
+ in nonKeyguardScenes -> flowOf(false)
+ in keyguardAgnosticScenes -> isDeviceNotEntered
+ else ->
+ throw IllegalStateException("Unknown scene: ${it.currentScene}")
+ }
+ }
+ is Transition -> {
+ when {
+ it.isTransitioningSets(from = keyguardScenes) -> flowOf(true)
+ it.isTransitioningSets(from = nonKeyguardScenes) -> flowOf(false)
+ it.isTransitioningSets(from = keyguardAgnosticScenes) ->
+ isDeviceNotEntered
+ else ->
+ throw IllegalStateException("Unknown scene: ${it.fromContent}")
+ }
+ }
+ }
+ },
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair,
+ )
+ .map { (lockscreenVisibilityByTransitionState, canWakeDirectlyToGone) ->
+ lockscreenVisibilityByTransitionState && !canWakeDirectlyToGone
+ }
+
+ private val lockscreenVisibilityLegacy =
+ combine(
+ transitionInteractor.currentKeyguardState,
+ wakeToGoneInteractor.canWakeDirectlyToGone,
+ ::Pair,
+ )
+ .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
+ .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
+ val startedFromStep = startedWithPrev.previousValue
+ val startedStep = startedWithPrev.newValue
+ val returningToGoneAfterCancellation =
+ startedStep.to == KeyguardState.GONE &&
+ startedFromStep.transitionState == TransitionState.CANCELED &&
+ startedFromStep.from == KeyguardState.GONE
+
+ val transitionInfo =
+ if (transitionRaceCondition()) {
+ transitionRepository.currentTransitionInfo
+ } else {
+ transitionRepository.currentTransitionInfoInternal.value
+ }
+ val wakingDirectlyToGone =
+ deviceIsAsleepInState(transitionInfo.from) &&
+ transitionInfo.to == KeyguardState.GONE
+
+ if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
+ // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
+ // which means we never want to show the lockscreen throughout the
+ // transition. Same for waking directly to gone, due to the lockscreen being
+ // disabled or because the device was woken back up before the lock timeout
+ // duration elapsed.
+ false
+ } else if (canWakeDirectlyToGone) {
+ // Never show the lockscreen if we can wake directly to GONE. This means
+ // that the lock timeout has not yet elapsed, or the keyguard is disabled.
+ // In either case, we don't show the activity lock screen until one of those
+ // conditions changes.
+ false
+ } else if (
+ currentState == KeyguardState.DREAMING &&
+ deviceEntryInteractor.get().isUnlocked.value
+ ) {
+ // Dreams dismiss keyguard and return to GONE if they can.
+ false
+ } else if (
+ startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
+ startedWithPrev.newValue.to == KeyguardState.GONE
+ ) {
+ // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
+ // when an app uses intent flags to launch over an insecure keyguard without
+ // dismissing it, and then manually requests keyguard dismissal while
+ // OCCLUDED. This transition is not user-visible; the device unlocks in the
+ // background and the app remains on top, while we're now GONE. In this case
+ // we should simply tell WM that the lockscreen is no longer visible, and
+ // *not* play the going away animation or related animations.
+ false
+ } else {
+ currentState != KeyguardState.GONE
+ }
+ }
+
+ /**
* Whether the lockscreen is visible, from the Window Manager (WM) perspective.
*
* Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
@@ -227,69 +328,11 @@ constructor(
*/
val lockscreenVisibility: Flow<Boolean> =
if (SceneContainerFlag.isEnabled) {
- isDeviceNotEntered
- } else {
- combine(
- transitionInteractor.currentKeyguardState,
- wakeToGoneInteractor.canWakeDirectlyToGone,
- ::Pair,
- )
- .sample(transitionInteractor.startedStepWithPrecedingStep, ::toTriple)
- .map { (currentState, canWakeDirectlyToGone, startedWithPrev) ->
- val startedFromStep = startedWithPrev.previousValue
- val startedStep = startedWithPrev.newValue
- val returningToGoneAfterCancellation =
- startedStep.to == KeyguardState.GONE &&
- startedFromStep.transitionState == TransitionState.CANCELED &&
- startedFromStep.from == KeyguardState.GONE
-
- val transitionInfo =
- if (transitionRaceCondition()) {
- transitionRepository.currentTransitionInfo
- } else {
- transitionRepository.currentTransitionInfoInternal.value
- }
- val wakingDirectlyToGone =
- deviceIsAsleepInState(transitionInfo.from) &&
- transitionInfo.to == KeyguardState.GONE
-
- if (returningToGoneAfterCancellation || wakingDirectlyToGone) {
- // GONE -> AOD/DOZING (cancel) -> GONE is the camera launch transition,
- // which means we never want to show the lockscreen throughout the
- // transition. Same for waking directly to gone, due to the lockscreen being
- // disabled or because the device was woken back up before the lock timeout
- // duration elapsed.
- false
- } else if (canWakeDirectlyToGone) {
- // Never show the lockscreen if we can wake directly to GONE. This means
- // that the lock timeout has not yet elapsed, or the keyguard is disabled.
- // In either case, we don't show the activity lock screen until one of those
- // conditions changes.
- false
- } else if (
- currentState == KeyguardState.DREAMING &&
- deviceEntryInteractor.get().isUnlocked.value
- ) {
- // Dreams dismiss keyguard and return to GONE if they can.
- false
- } else if (
- startedWithPrev.newValue.from == KeyguardState.OCCLUDED &&
- startedWithPrev.newValue.to == KeyguardState.GONE
- ) {
- // OCCLUDED -> GONE directly, without transiting a *_BOUNCER state, occurs
- // when an app uses intent flags to launch over an insecure keyguard without
- // dismissing it, and then manually requests keyguard dismissal while
- // OCCLUDED. This transition is not user-visible; the device unlocks in the
- // background and the app remains on top, while we're now GONE. In this case
- // we should simply tell WM that the lockscreen is no longer visible, and
- // *not* play the going away animation or related animations.
- false
- } else {
- currentState != KeyguardState.GONE
- }
- }
- .distinctUntilChanged()
- }
+ lockscreenVisibilityWithScenes
+ } else {
+ lockscreenVisibilityLegacy
+ }
+ .distinctUntilChanged()
/**
* Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index be4bc2305922..69856151e41c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -182,9 +182,11 @@ object DeviceEntryIconViewBinder {
fgIconView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Start with an empty state
+ Log.d(TAG, "Initializing device entry fgIconView")
fgIconView.setImageState(StateSet.NOTHING, /* merge */ false)
launch("$TAG#fpIconView.viewModel") {
fgViewModel.viewModel.collect { viewModel ->
+ Log.d(TAG, "Updating device entry icon image state $viewModel")
fgIconView.setImageState(
view.getIconState(viewModel.type, viewModel.useAodVariant),
/* merge */ false,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
index c78e0c9f5266..478372d4dc0c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -30,6 +30,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.ClockSize
+import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.ui.StateToValue
import com.android.systemui.res.R
@@ -42,8 +43,10 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
@@ -164,9 +167,17 @@ constructor(
private fun burnIn(params: BurnInParameters): Flow<BurnInModel> {
return combine(
- keyguardTransitionInteractor.transitionValue(KeyguardState.AOD).map {
- Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it)
- },
+ merge(
+ keyguardTransitionInteractor.transition(Edge.create(to = KeyguardState.AOD)),
+ keyguardTransitionInteractor
+ .transition(Edge.create(from = KeyguardState.AOD))
+ .map { it.copy(value = 1f - it.value) },
+ keyguardTransitionInteractor
+ .transition(Edge.create(to = KeyguardState.LOCKSCREEN))
+ .filter { it.from != KeyguardState.AOD }
+ .map { it.copy(value = 0f) },
+ )
+ .map { Interpolators.FAST_OUT_SLOW_IN.getInterpolation(it.value) },
burnInInteractor.burnIn(
xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
yDimenResourceId = R.dimen.burn_in_prevention_offset_y,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
index 67b009e50ce0..7f3ef61d02c0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/GlanceableHubToLockscreenTransitionViewModel.kt
@@ -54,9 +54,7 @@ constructor(
duration = TO_LOCKSCREEN_DURATION,
edge = Edge.create(from = Scenes.Communal, to = LOCKSCREEN),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = GLANCEABLE_HUB, to = LOCKSCREEN))
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
@@ -75,7 +73,7 @@ constructor(
configurationInteractor
.directionalDimensionPixelSize(
LayoutDirection.LTR,
- R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x
+ R.dimen.hub_to_lockscreen_transition_lockscreen_translation_x,
)
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
@@ -87,7 +85,7 @@ constructor(
// is cancelled.
onFinish = { 0f },
onCancel = { 0f },
- name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX"
+ name = "GLANCEABLE_HUB->LOCKSCREEN: keyguardTranslationX",
)
}
@@ -95,6 +93,8 @@ constructor(
val shortcutsAlpha: Flow<Float> = keyguardAlpha
+ val statusBarAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
index 378374e72c8b..dd8ff8c4a052 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToGlanceableHubTransitionViewModel.kt
@@ -54,9 +54,7 @@ constructor(
duration = FromLockscreenTransitionInteractor.TO_GLANCEABLE_HUB_DURATION,
edge = Edge.create(from = LOCKSCREEN, to = Scenes.Communal),
)
- .setupWithoutSceneContainer(
- edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB),
- )
+ .setupWithoutSceneContainer(edge = Edge.create(from = LOCKSCREEN, to = GLANCEABLE_HUB))
val keyguardAlpha: Flow<Float> =
transitionAnimation.sharedFlow(
@@ -74,7 +72,7 @@ constructor(
configurationInteractor
.directionalDimensionPixelSize(
LayoutDirection.LTR,
- R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x
+ R.dimen.lockscreen_to_hub_transition_lockscreen_translation_x,
)
.flatMapLatest { translatePx: Int ->
transitionAnimation.sharedFlowWithState(
@@ -86,7 +84,7 @@ constructor(
onFinish = { 0f },
onCancel = { 0f },
interpolator = EMPHASIZED,
- name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX"
+ name = "LOCKSCREEN->GLANCEABLE_HUB: keyguardTranslationX",
)
}
@@ -94,6 +92,8 @@ constructor(
val shortcutsAlpha: Flow<Float> = keyguardAlpha
+ val statusBarAlpha: Flow<Float> = keyguardAlpha
+
val notificationTranslationX: Flow<Float> =
keyguardTranslationX.map { it.value }.filterNotNull()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
new file mode 100644
index 000000000000..a33685b61237
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactory.kt
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import android.content.Context
+import android.graphics.drawable.Animatable
+import android.graphics.drawable.Drawable
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.WorkerThread
+import androidx.media.utils.MediaConstants
+import androidx.media3.common.Player
+import androidx.media3.session.CommandButton
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionToken
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.graphics.ImageLoader
+import com.android.systemui.media.controls.shared.MediaControlDrawables
+import com.android.systemui.media.controls.shared.MediaLogger
+import com.android.systemui.media.controls.shared.model.MediaAction
+import com.android.systemui.media.controls.shared.model.MediaButton
+import com.android.systemui.media.controls.util.MediaControllerFactory
+import com.android.systemui.media.controls.util.SessionTokenFactory
+import com.android.systemui.res.R
+import com.android.systemui.util.Assert
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+private const val TAG = "Media3ActionFactory"
+
+@SysUISingleton
+class Media3ActionFactory
+@Inject
+constructor(
+ @Application val context: Context,
+ private val imageLoader: ImageLoader,
+ private val controllerFactory: MediaControllerFactory,
+ private val tokenFactory: SessionTokenFactory,
+ private val logger: MediaLogger,
+ @Background private val looper: Looper,
+ @Background private val handler: Handler,
+ @Background private val bgScope: CoroutineScope,
+) {
+
+ /**
+ * Generates action button info for this media session based on the Media3 session info
+ *
+ * @param packageName Package name for the media app
+ * @param controller The framework [MediaController] for the session
+ * @return The media action buttons, or null if the session token is null
+ */
+ suspend fun createActionsFromSession(
+ packageName: String,
+ sessionToken: MediaSession.Token,
+ ): MediaButton? {
+ // Get the Media3 controller using the legacy token
+ val token = tokenFactory.createTokenFromLegacy(sessionToken)
+ val m3controller = controllerFactory.create(token, looper)
+
+ // Build button info
+ val buttons = suspendCancellableCoroutine { continuation ->
+ // Media3Controller methods must always be called from a specific looper
+ handler.post {
+ val result = getMedia3Actions(packageName, m3controller, token)
+ m3controller.release()
+ continuation.resumeWith(Result.success(result))
+ }
+ }
+ return buttons
+ }
+
+ /** This method must be called on the Media3 looper! */
+ @WorkerThread
+ private fun getMedia3Actions(
+ packageName: String,
+ m3controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ ): MediaButton? {
+ Assert.isNotMainThread()
+
+ // First, get standard actions
+ val playOrPause =
+ if (m3controller.playbackState == Player.STATE_BUFFERING) {
+ // Spinner needs to be animating to render anything. Start it here.
+ val drawable =
+ context.getDrawable(com.android.internal.R.drawable.progress_small_material)
+ (drawable as Animatable).start()
+ MediaAction(
+ drawable,
+ null, // no action to perform when clicked
+ context.getString(R.string.controls_media_button_connecting),
+ context.getDrawable(R.drawable.ic_media_connecting_container),
+ // Specify a rebind id to prevent the spinner from restarting on later binds.
+ com.android.internal.R.drawable.progress_small_material,
+ )
+ } else {
+ getStandardAction(m3controller, token, Player.COMMAND_PLAY_PAUSE)
+ }
+
+ val prevButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_PREVIOUS,
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
+ )
+ val nextButton =
+ getStandardAction(
+ m3controller,
+ token,
+ Player.COMMAND_SEEK_TO_NEXT,
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
+ )
+
+ // Then, get custom actions
+ var customActions =
+ m3controller.customLayout
+ .asSequence()
+ .filter {
+ it.isEnabled &&
+ it.sessionCommand?.commandCode == SessionCommand.COMMAND_CODE_CUSTOM &&
+ m3controller.isSessionCommandAvailable(it.sessionCommand!!)
+ }
+ .map { getCustomAction(packageName, token, it) }
+ .iterator()
+ fun nextCustomAction() = if (customActions.hasNext()) customActions.next() else null
+
+ // Finally, assign the remaining button slots: play/pause A B C D
+ // A = previous, else custom action (if not reserved)
+ // B = next, else custom action (if not reserved)
+ // C and D are always custom actions
+ val reservePrev =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_PREV,
+ false,
+ )
+ val reserveNext =
+ m3controller.sessionExtras.getBoolean(
+ MediaConstants.SESSION_EXTRAS_KEY_SLOT_RESERVATION_SKIP_TO_NEXT,
+ false,
+ )
+
+ val prevOrCustom =
+ prevButton
+ ?: if (reservePrev) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ val nextOrCustom =
+ nextButton
+ ?: if (reserveNext) {
+ null
+ } else {
+ nextCustomAction()
+ }
+
+ return MediaButton(
+ playOrPause = playOrPause,
+ nextOrCustom = nextOrCustom,
+ prevOrCustom = prevOrCustom,
+ custom0 = nextCustomAction(),
+ custom1 = nextCustomAction(),
+ reserveNext = reserveNext,
+ reservePrev = reservePrev,
+ )
+ }
+
+ /**
+ * Create a [MediaAction] for a given command, if supported
+ *
+ * @param controller Media3 controller for the session
+ * @param commands Commands to check, in priority order
+ * @return A [MediaAction] representing the first supported command, or null if not supported
+ */
+ private fun getStandardAction(
+ controller: androidx.media3.session.MediaController,
+ token: SessionToken,
+ vararg commands: @Player.Command Int,
+ ): MediaAction? {
+ for (command in commands) {
+ if (!controller.isCommandAvailable(command)) {
+ continue
+ }
+
+ return when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (!controller.isPlaying) {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_play),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_play),
+ context.getDrawable(R.drawable.ic_media_play_container),
+ )
+ } else {
+ MediaAction(
+ context.getDrawable(R.drawable.ic_media_pause),
+ { executeAction(token, Player.COMMAND_PLAY_PAUSE) },
+ context.getString(R.string.controls_media_button_pause),
+ context.getDrawable(R.drawable.ic_media_pause_container),
+ )
+ }
+ }
+ else -> {
+ MediaAction(
+ icon = getIconForAction(command),
+ action = { executeAction(token, command) },
+ contentDescription = getDescriptionForAction(command),
+ background = null,
+ )
+ }
+ }
+ }
+ return null
+ }
+
+ /** Get a [MediaAction] representing a [CommandButton] */
+ private fun getCustomAction(
+ packageName: String,
+ token: SessionToken,
+ customAction: CommandButton,
+ ): MediaAction {
+ return MediaAction(
+ getIconForAction(customAction, packageName),
+ { executeAction(token, Player.COMMAND_INVALID, customAction) },
+ customAction.displayName,
+ null,
+ )
+ }
+
+ private fun getIconForAction(command: @Player.Command Int): Drawable? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM -> MediaControlDrawables.getPrevIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT -> MediaControlDrawables.getNextIcon(context)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> MediaControlDrawables.getNextIcon(context)
+ else -> {
+ Log.e(TAG, "Unknown icon for $command")
+ null
+ }
+ }
+ }
+
+ private fun getIconForAction(customAction: CommandButton, packageName: String): Drawable? {
+ val size = context.resources.getDimensionPixelSize(R.dimen.min_clickable_item_size)
+ // TODO(b/360196209): check customAction.icon field to use platform icons
+ if (customAction.iconResId != 0) {
+ val packageContext = context.createPackageContext(packageName, 0)
+ val source = ImageLoader.Res(customAction.iconResId, packageContext)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+
+ if (customAction.iconUri != null) {
+ val source = ImageLoader.Uri(customAction.iconUri!!)
+ return runBlocking { imageLoader.loadDrawable(source, size, size) }
+ }
+ return null
+ }
+
+ private fun getDescriptionForAction(command: @Player.Command Int): String? {
+ return when (command) {
+ Player.COMMAND_SEEK_TO_PREVIOUS ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_prev)
+ Player.COMMAND_SEEK_TO_NEXT -> context.getString(R.string.controls_media_button_next)
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM ->
+ context.getString(R.string.controls_media_button_next)
+ else -> {
+ Log.e(TAG, "Unknown content description for $command")
+ null
+ }
+ }
+ }
+
+ private fun executeAction(
+ token: SessionToken,
+ command: Int,
+ customAction: CommandButton? = null,
+ ) {
+ bgScope.launch {
+ val controller = controllerFactory.create(token, looper)
+ handler.post {
+ when (command) {
+ Player.COMMAND_PLAY_PAUSE -> {
+ if (controller.isPlaying) controller.pause() else controller.play()
+ }
+
+ Player.COMMAND_SEEK_TO_PREVIOUS -> controller.seekToPrevious()
+ Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM ->
+ controller.seekToPreviousMediaItem()
+
+ Player.COMMAND_SEEK_TO_NEXT -> controller.seekToNext()
+ Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM -> controller.seekToNextMediaItem()
+ Player.COMMAND_INVALID -> {
+ if (
+ customAction != null &&
+ customAction!!.sessionCommand != null &&
+ controller.isSessionCommandAvailable(
+ customAction!!.sessionCommand!!
+ )
+ ) {
+ controller.sendCustomCommand(
+ customAction!!.sessionCommand!!,
+ customAction!!.extras,
+ )
+ } else {
+ logger.logMedia3UnsupportedCommand("$command, action $customAction")
+ }
+ }
+
+ else -> logger.logMedia3UnsupportedCommand(command.toString())
+ }
+ controller.release()
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
index 591a9cccdadd..a176e0c1c2a6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoader.kt
@@ -84,6 +84,7 @@ constructor(
private val mediaFlags: MediaFlags,
private val imageLoader: ImageLoader,
private val statusBarManager: StatusBarManager,
+ private val media3ActionFactory: Media3ActionFactory,
) {
private val mediaProcessingJobs = ConcurrentHashMap<String, Job>()
@@ -364,7 +365,7 @@ constructor(
)
}
- private fun createActionsFromState(
+ private suspend fun createActionsFromState(
packageName: String,
controller: MediaController,
user: UserHandle,
@@ -373,6 +374,12 @@ constructor(
return null
}
+ if (mediaFlags.areMedia3ActionsEnabled(packageName, user)) {
+ return media3ActionFactory.createActionsFromSession(
+ packageName,
+ controller.sessionToken,
+ )
+ }
return createActionsFromState(context, packageName, controller)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
index 2bdee67dd57a..beb4d4103b11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaProcessingHelper.kt
@@ -141,6 +141,7 @@ private fun areActionsEqual(
new: MediaData,
old: MediaData,
): Boolean {
+ // TODO(b/360196209): account for actions generated from media3
val oldState = MediaController(context, old.token!!).playbackState
return if (
new.semanticActions == null &&
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
index 88c47ba4d243..0b598c13311f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/shared/MediaLogger.kt
@@ -140,6 +140,10 @@ class MediaLogger @Inject constructor(@MediaLog private val buffer: LogBuffer) {
)
}
+ fun logMedia3UnsupportedCommand(command: String) {
+ buffer.log(TAG, LogLevel.DEBUG, { str1 = command }, { "Unsupported media3 command $str1" })
+ }
+
companion object {
private const val TAG = "MediaLog"
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
deleted file mode 100644
index 6caf5c20b81c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.media.session.MediaController;
-import android.media.session.MediaSession;
-
-import javax.inject.Inject;
-
-/**
- * Testable wrapper around {@link MediaController} constructor.
- */
-public class MediaControllerFactory {
-
- private final Context mContext;
-
- @Inject
- public MediaControllerFactory(Context context) {
- mContext = context;
- }
-
- /**
- * Creates a new MediaController from a session's token.
- *
- * @param token The token for the session. This value must never be null.
- */
- public MediaController create(@NonNull MediaSession.Token token) {
- return new MediaController(mContext, token);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
new file mode 100644
index 000000000000..741f52998782
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControllerFactory.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaController
+import android.media.session.MediaSession
+import android.os.Looper
+import androidx.concurrent.futures.await
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for media controller construction */
+open class MediaControllerFactory @Inject constructor(private val context: Context) {
+ /**
+ * Creates a new [MediaController] from the framework session token.
+ *
+ * @param token The token for the session. This value must never be null.
+ */
+ open fun create(token: MediaSession.Token): MediaController {
+ return MediaController(context, token)
+ }
+
+ /** Creates a new [Media3Controller] from a [SessionToken] */
+ open suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return Media3Controller.Builder(context, token)
+ .setApplicationLooper(looper)
+ .buildAsync()
+ .await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index d4af1b546369..ac60c47ee6ab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -18,9 +18,10 @@ package com.android.systemui.media.controls.util
import android.app.StatusBarManager
import android.os.UserHandle
+import com.android.systemui.Flags
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags as FlagsClassic
import javax.inject.Inject
@SysUISingleton
@@ -29,22 +30,29 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass
* Check whether media control actions should be based on PlaybackState instead of notification
*/
fun areMediaSessionActionsEnabled(packageName: String, user: UserHandle): Boolean {
- // Allow global override with flag
return StatusBarManager.useMediaSessionActionsForApp(packageName, user)
}
+ /** Check whether media control actions should be derived from Media3 controller */
+ fun areMedia3ActionsEnabled(packageName: String, user: UserHandle): Boolean {
+ val compatFlag = StatusBarManager.useMedia3ControllerForApp(packageName, user)
+ val featureFlag = Flags.mediaControlsButtonMedia3()
+ return featureFlag && compatFlag
+ }
+
/**
* If true, keep active media controls for the lifetime of the MediaSession, regardless of
* whether the underlying notification was dismissed
*/
- fun isRetainingPlayersEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_SESSIONS)
+ fun isRetainingPlayersEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_SESSIONS)
/** Check whether to get progress information for resume players */
- fun isResumeProgressEnabled() = featureFlags.isEnabled(Flags.MEDIA_RESUME_PROGRESS)
+ fun isResumeProgressEnabled() = featureFlags.isEnabled(FlagsClassic.MEDIA_RESUME_PROGRESS)
/** If true, do not automatically dismiss the recommendation card */
- fun isPersistentSsCardEnabled() = featureFlags.isEnabled(Flags.MEDIA_RETAIN_RECOMMENDATIONS)
+ fun isPersistentSsCardEnabled() =
+ featureFlags.isEnabled(FlagsClassic.MEDIA_RETAIN_RECOMMENDATIONS)
/** Check whether we allow remote media to generate resume controls */
- fun isRemoteResumeAllowed() = featureFlags.isEnabled(Flags.MEDIA_REMOTE_RESUME)
+ fun isRemoteResumeAllowed() = featureFlags.isEnabled(FlagsClassic.MEDIA_REMOTE_RESUME)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
new file mode 100644
index 000000000000..b289fd40a3a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/SessionTokenFactory.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession
+import androidx.concurrent.futures.await
+import androidx.media3.session.SessionToken
+import javax.inject.Inject
+
+/** Testable wrapper for [SessionToken] creation */
+open class SessionTokenFactory @Inject constructor(private val context: Context) {
+ /** Create a new [SessionToken] from the framework [MediaSession.Token] */
+ open suspend fun createTokenFromLegacy(token: MediaSession.Token): SessionToken {
+ return SessionToken.createSessionToken(context, token).await()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index bf2aa7efc0c4..56885c3eea9f 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -18,7 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data
import android.annotation.ColorInt
import android.annotation.UserIdInt
-import android.app.ActivityManager.RecentTaskInfo
+import android.app.TaskInfo
import android.content.ComponentName
import com.android.wm.shell.shared.split.SplitBounds
@@ -34,7 +34,7 @@ data class RecentTask(
val splitBounds: SplitBounds?,
) {
constructor(
- taskInfo: RecentTaskInfo,
+ taskInfo: TaskInfo,
isForegroundTask: Boolean,
userType: UserType,
splitBounds: SplitBounds? = null
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 82e58cc7f1d9..d94424c59376 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -23,7 +23,7 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.kotlin.getOrNull
import com.android.wm.shell.recents.RecentTasks
-import com.android.wm.shell.shared.GroupedRecentTaskInfo
+import com.android.wm.shell.shared.GroupedTaskInfo
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -51,7 +51,7 @@ constructor(
override suspend fun loadRecentTasks(): List<RecentTask> =
withContext(coroutineDispatcher) {
- val groupedTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+ val groupedTasks: List<GroupedTaskInfo> = recents?.getTasks() ?: emptyList()
// Note: the returned task list is from the most-recent to least-recent order.
// When opening the app selector in full screen, index 0 will be just the app selector
// activity and a null second task, so the foreground task will be index 1, but when
@@ -86,7 +86,7 @@ constructor(
}
}
- private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
+ private suspend fun RecentTasks.getTasks(): List<GroupedTaskInfo> =
suspendCoroutine { continuation ->
getRecentTasks(
Integer.MAX_VALUE,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
index 96c0cac53908..40613c0edc68 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/views/NavigationBar.java
@@ -149,6 +149,7 @@ import com.android.systemui.statusbar.CommandQueue.Callbacks;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -258,8 +259,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private boolean mTransientShownFromGestureOnSystemBar;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
private LightBarController mLightBarController;
- private final LightBarController mMainLightBarController;
- private final LightBarController.Factory mLightBarControllerFactory;
+ private final LightBarControllerStore mLightBarControllerStore;
private AutoHideController mAutoHideController;
private final AutoHideController mMainAutoHideController;
private final AutoHideController.Factory mAutoHideControllerFactory;
@@ -580,8 +580,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
@Background Executor bgExecutor,
UiEventLogger uiEventLogger,
NavBarHelper navBarHelper,
- LightBarController mainLightBarController,
- LightBarController.Factory lightBarControllerFactory,
+ LightBarControllerStore lightBarControllerStore,
AutoHideController mainAutoHideController,
AutoHideController.Factory autoHideControllerFactory,
Optional<TelecomManager> telecomManagerOptional,
@@ -628,8 +627,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mUiEventLogger = uiEventLogger;
mNavBarHelper = navBarHelper;
mNotificationShadeDepthController = notificationShadeDepthController;
- mMainLightBarController = mainLightBarController;
- mLightBarControllerFactory = lightBarControllerFactory;
+ mLightBarControllerStore = lightBarControllerStore;
mMainAutoHideController = mainAutoHideController;
mAutoHideControllerFactory = autoHideControllerFactory;
mTelecomManagerOptional = telecomManagerOptional;
@@ -842,8 +840,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
// Unfortunately, we still need it because status bar needs LightBarController
// before notifications creation. We cannot directly use getLightBarController()
// from NavigationBarFragment directly.
- LightBarController lightBarController = mIsOnDefaultDisplay
- ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
+ LightBarController lightBarController = mLightBarControllerStore.forDisplay(mDisplayId);
setLightBarController(lightBarController);
// TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 1c9cb3d99480..fef5a745c1ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -25,6 +25,7 @@ import androidx.annotation.VisibleForTesting
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.MetricsLogger
import com.android.systemui.animation.Expandable
import com.android.systemui.dagger.qualifiers.Background
@@ -48,7 +49,6 @@ import com.android.systemui.qs.tiles.viewmodel.QSTileConfigProvider
import com.android.systemui.qs.tiles.viewmodel.QSTileState
import com.android.systemui.res.R
import javax.inject.Inject
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.runBlocking
class ModesTile
@@ -120,8 +120,7 @@ constructor(
tileState = tileMapper.map(config, model)
state?.apply {
this.state = tileState.activationState.legacyState
- val tileStateIcon = tileState.icon()
- icon = tileStateIcon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
+ icon = tileState.icon?.asQSTileIcon() ?: ResourceIcon.get(ICON_RES_ID)
label = tileLabel
secondaryLabel = tileState.secondaryLabel
contentDescription = tileState.contentDescription
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index 9fb1d46c4241..d67057a2f476 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [AirplaneModeTileModel] to [QSTileState]. */
class AirplaneModeMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- val theme: Theme,
-) : QSTileDataToStateMapper<AirplaneModeTileModel> {
+constructor(@Main private val resources: Resources, val theme: Theme) :
+ QSTileDataToStateMapper<AirplaneModeTileModel> {
override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,16 +41,7 @@ constructor(
} else {
R.drawable.qs_airplane_icon_off
}
-
- icon = {
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = resources.getStringArray(R.array.tile_states_airplane)[2]
@@ -62,9 +51,6 @@ constructor(
}
contentDescription = label
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index f0889433094a..7322b8d098fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -45,6 +45,7 @@ constructor(
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
val formatterDateOnly: DateTimeFormatter = DateTimeFormatter.ofPattern("E MMM d")
}
+
override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
when (data) {
@@ -54,13 +55,13 @@ constructor(
val alarmDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(data.alarmClockInfo.triggerTime),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
val nowDateTime =
LocalDateTime.ofInstant(
Instant.ofEpochMilli(clock.currentTimeMillis()),
- TimeZone.getDefault().toZoneId()
+ TimeZone.getDefault().toZoneId(),
)
// Edge case: If it's 8:00:30 right now and alarm is requested for next week at
@@ -84,7 +85,7 @@ constructor(
}
}
iconRes = R.drawable.ic_alarm
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
index bcf0935adf85..5b30e8d2c86b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/battery/ui/BatterySaverTileMapper.kt
@@ -29,10 +29,8 @@ import javax.inject.Inject
/** Maps [BatterySaverTileModel] to [QSTileState]. */
open class BatterySaverTileMapper
@Inject
-constructor(
- @Main protected val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<BatterySaverTileModel> {
+constructor(@Main protected val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<BatterySaverTileModel> {
override fun map(config: QSTileConfig, data: BatterySaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -41,8 +39,7 @@ constructor(
iconRes =
if (data.isPowerSaving) R.drawable.qs_battery_saver_icon_on
else R.drawable.qs_battery_saver_icon_off
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isPluggedIn) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
index cad7c65ad112..7c90b3d87958 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -17,6 +17,7 @@
package com.android.systemui.qs.tiles.impl.colorcorrection.domain
import android.content.res.Resources
+import com.android.systemui.common.shared.model.Icon
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
@@ -28,17 +29,14 @@ import javax.inject.Inject
/** Maps [ColorCorrectionTileModel] to [QSTileState]. */
class ColorCorrectionTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ColorCorrectionTileModel> {
override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
-
iconRes = R.drawable.ic_qs_color_correction
-
+ icon = Icon.Loaded(resources.getDrawable(R.drawable.ic_qs_color_correction)!!, null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
index 984228d80b7f..60aa4ea4759f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -35,10 +35,8 @@ import javax.inject.Inject
@SysUISingleton
class CustomTileMapper
@Inject
-constructor(
- private val context: Context,
- private val uriGrantsManager: IUriGrantsManager,
-) : QSTileDataToStateMapper<CustomTileDataModel> {
+constructor(private val context: Context, private val uriGrantsManager: IUriGrantsManager) :
+ QSTileDataToStateMapper<CustomTileDataModel> {
override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
val userContext =
@@ -50,7 +48,7 @@ constructor(
val iconResult =
if (userContext != null) {
- getIconProvider(
+ getIcon(
userContext = userContext,
icon = data.tile.icon,
callingAppUid = data.callingAppUid,
@@ -58,16 +56,16 @@ constructor(
defaultIcon = data.defaultTileIcon,
)
} else {
- IconResult({ null }, true)
+ IconResult(null, true)
}
- return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+ return QSTileState.build(iconResult.icon, data.tile.label) {
var tileState: Int = data.tile.state
if (data.hasPendingBind) {
tileState = Tile.STATE_UNAVAILABLE
}
- icon = iconResult.iconProvider
+ icon = iconResult.icon
activationState =
if (iconResult.failedToLoad) {
QSTileState.ActivationState.UNAVAILABLE
@@ -102,7 +100,7 @@ constructor(
}
@SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
- private fun getIconProvider(
+ private fun getIcon(
userContext: Context,
icon: android.graphics.drawable.Icon?,
callingAppUid: Int,
@@ -123,17 +121,12 @@ constructor(
null
} ?: defaultIcon?.loadDrawable(userContext)
return IconResult(
- {
- drawable?.constantState?.newDrawable()?.let {
- Icon.Loaded(it, contentDescription = null)
- }
+ drawable?.constantState?.newDrawable()?.let {
+ Icon.Loaded(it, contentDescription = null)
},
failedToLoad,
)
}
- class IconResult(
- val iconProvider: () -> Icon?,
- val failedToLoad: Boolean,
- )
+ class IconResult(val icon: Icon?, val failedToLoad: Boolean)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index d7d61241fc6c..7e557ebe4639 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [FlashlightTileModel] to [QSTileState]. */
class FlashlightMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<FlashlightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<FlashlightTileModel> {
override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,15 +41,8 @@ constructor(
} else {
R.drawable.qs_flashlight_icon_off
}
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
index 6b4dda13a5e6..9d44fc6ae25e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/fontscaling/domain/FontScalingTileMapper.kt
@@ -29,23 +29,13 @@ import javax.inject.Inject
/** Maps [FontScalingTileModel] to [QSTileState]. */
class FontScalingTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<FontScalingTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<FontScalingTileModel> {
override fun map(config: QSTileConfig, data: FontScalingTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
iconRes = R.drawable.ic_qs_font_scaling
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
activationState = QSTileState.ActivationState.ACTIVE
sideViewIcon = QSTileState.SideViewIcon.Chevron
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
index 8dd611f9911a..c3ac1f8d9a72 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt
@@ -36,9 +36,7 @@ constructor(@Main private val resources: Resources, private val theme: Resources
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_hearing_devices_label)
iconRes = R.drawable.qs_hearing_devices_icon
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
contentDescription = label
if (data.isAnyActiveHearingDevice) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
index bb0b9b7084fa..fc945851cdad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/internet/domain/InternetTileMapper.kt
@@ -61,28 +61,26 @@ constructor(
when (val dataIcon = data.icon) {
is InternetTileIconModel.ResourceId -> {
iconRes = dataIcon.resId
- icon = {
+ icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resId, theme),
contentDescription = null,
)
- }
}
is InternetTileIconModel.Cellular -> {
val signalDrawable = SignalDrawable(context, handler)
signalDrawable.setLevel(dataIcon.level)
- icon = { Icon.Loaded(signalDrawable, contentDescription = null) }
+ icon = Icon.Loaded(signalDrawable, contentDescription = null)
}
is InternetTileIconModel.Satellite -> {
iconRes = dataIcon.resourceIcon.res // level is inferred from res
- icon = {
+ icon =
Icon.Loaded(
resources.getDrawable(dataIcon.resourceIcon.res, theme),
contentDescription = null,
)
- }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
index 40aee65f41a7..3692c35472f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/inversion/domain/ColorInversionTileMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [ColorInversionTileModel] to [QSTileState]. */
class ColorInversionTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<ColorInversionTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<ColorInversionTileModel> {
override fun map(config: QSTileConfig, data: ColorInversionTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_inversion)
@@ -47,7 +45,7 @@ constructor(
secondaryLabel = subtitleArray[1]
iconRes = R.drawable.qs_invert_colors_icon_off
}
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
index ff931b35567f..3fe2a7734801 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/irecording/IssueRecordingMapper.kt
@@ -28,21 +28,26 @@ import javax.inject.Inject
class IssueRecordingMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<IssueRecordingModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<IssueRecordingModel> {
override fun map(config: QSTileConfig, data: IssueRecordingModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- if (data.isRecording) {
- activationState = QSTileState.ActivationState.ACTIVE
- secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
- icon = { Icon.Resource(R.drawable.qs_record_issue_icon_on, null) }
- } else {
- icon = { Icon.Resource(R.drawable.qs_record_issue_icon_off, null) }
- activationState = QSTileState.ActivationState.INACTIVE
- secondaryLabel = resources.getString(R.string.qs_record_issue_start)
- }
+ icon =
+ if (data.isRecording) {
+ activationState = QSTileState.ActivationState.ACTIVE
+ secondaryLabel = resources.getString(R.string.qs_record_issue_stop)
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_record_issue_icon_on, theme),
+ null,
+ )
+ } else {
+ activationState = QSTileState.ActivationState.INACTIVE
+ secondaryLabel = resources.getString(R.string.qs_record_issue_start)
+ Icon.Loaded(
+ resources.getDrawable(R.drawable.qs_record_issue_icon_off, theme),
+ null,
+ )
+ }
supportedActions = setOf(QSTileState.UserAction.CLICK)
contentDescription = "$label, $secondaryLabel"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index d58f5abcd018..08432f685ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [LocationTileModel] to [QSTileState]. */
class LocationTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<LocationTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<LocationTileModel> {
override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -43,17 +41,9 @@ constructor(
} else {
R.drawable.qs_location_icon_off
}
- val icon =
- Icon.Loaded(
- resources.getDrawable(
- iconRes!!,
- theme,
- ),
- contentDescription = null
- )
- this.icon = { icon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- this.label = resources.getString(R.string.quick_settings_location_label)
+ label = resources.getString(R.string.quick_settings_location_label)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
index 69da3134314b..4a6431359ca2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt
@@ -30,14 +30,12 @@ import javax.inject.Inject
class ModesTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ModesTileModel> {
+constructor(@Main private val resources: Resources, val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ModesTileModel> {
override fun map(config: QSTileConfig, data: ModesTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
iconRes = data.iconResId
- icon = { data.icon }
+ icon = data.icon
activationState =
if (data.isActivated) {
QSTileState.ActivationState.ACTIVE
@@ -47,10 +45,7 @@ constructor(
secondaryLabel = getModesStatus(data, resources)
contentDescription = "$label. $secondaryLabel"
supportedActions =
- setOf(
- QSTileState.UserAction.CLICK,
- QSTileState.UserAction.LONG_CLICK,
- )
+ setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
sideViewIcon = QSTileState.SideViewIcon.Chevron
expandedAccessibilityClass = Button::class
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
index bcf7cc763b9e..081a03c7ae67 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/night/ui/NightDisplayTileMapper.kt
@@ -57,9 +57,8 @@ constructor(
activationState = QSTileState.ActivationState.INACTIVE
iconRes = R.drawable.qs_nightlight_icon_off
}
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
secondaryLabel = getSecondaryLabel(data, resources)
@@ -70,7 +69,7 @@ constructor(
private fun getSecondaryLabel(
data: NightDisplayTileModel,
- resources: Resources
+ resources: Resources,
): CharSequence? {
when (data) {
is NightDisplayTileModel.AutoModeTwilight -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
index 40809960735f..8e5d0d4eb3dc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/onehanded/ui/OneHandedModeTileMapper.kt
@@ -29,17 +29,15 @@ import javax.inject.Inject
/** Maps [OneHandedModeTileModel] to [QSTileState]. */
class OneHandedModeTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<OneHandedModeTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<OneHandedModeTileModel> {
override fun map(config: QSTileConfig, data: OneHandedModeTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
val subtitleArray = resources.getStringArray(R.array.tile_states_onehanded)
label = resources.getString(R.string.quick_settings_onehanded_label)
iconRes = com.android.internal.R.drawable.ic_qs_one_handed_mode
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (data.isEnabled) {
activationState = QSTileState.ActivationState.ACTIVE
secondaryLabel = subtitleArray[2]
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
index 823174234b13..5c6351e88494 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/qr/ui/QRCodeScannerTileMapper.kt
@@ -29,17 +29,15 @@ import javax.inject.Inject
/** Maps [QRCodeScannerTileModel] to [QSTileState]. */
class QRCodeScannerTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<QRCodeScannerTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<QRCodeScannerTileModel> {
override fun map(config: QSTileConfig, data: QRCodeScannerTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.qr_code_scanner_title)
contentDescription = label
iconRes = R.drawable.ic_qr_code_scanner
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.Chevron
supportedActions = setOf(QSTileState.UserAction.CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
index 85ee02207ac6..fe77fe61b4bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/reducebrightness/ui/ReduceBrightColorsTileMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [ReduceBrightColorsTileModel] to [QSTileState]. */
class ReduceBrightColorsTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ReduceBrightColorsTileModel> {
override fun map(config: QSTileConfig, data: ReduceBrightColorsTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
@@ -50,12 +48,7 @@ constructor(
resources
.getStringArray(R.array.tile_states_reduce_brightness)[Tile.STATE_INACTIVE]
}
- icon = {
- Icon.Loaded(
- drawable = resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
label =
resources.getString(com.android.internal.R.string.reduce_bright_colors_feature_name)
contentDescription = label
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
index 33dc6ed7a1e8..9a003ffdf7de 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/rotation/ui/mapper/RotationLockTileMapper.kt
@@ -36,36 +36,33 @@ constructor(
@Main private val resources: Resources,
private val theme: Resources.Theme,
private val devicePostureController: DevicePostureController,
- private val deviceStateManager: DeviceStateManager
+ private val deviceStateManager: DeviceStateManager,
) : QSTileDataToStateMapper<RotationLockTileModel> {
override fun map(config: QSTileConfig, data: RotationLockTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
- this.label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
- this.contentDescription =
- resources.getString(R.string.accessibility_quick_settings_rotation)
+ label = resources.getString(R.string.quick_settings_rotation_unlocked_label)
+ contentDescription = resources.getString(R.string.accessibility_quick_settings_rotation)
if (data.isRotationLocked) {
activationState = QSTileState.ActivationState.INACTIVE
- this.secondaryLabel = EMPTY_SECONDARY_STRING
+ secondaryLabel = EMPTY_SECONDARY_STRING
iconRes = R.drawable.qs_auto_rotate_icon_off
} else {
activationState = QSTileState.ActivationState.ACTIVE
- this.secondaryLabel =
+ secondaryLabel =
if (data.isCameraRotationEnabled) {
resources.getString(R.string.rotation_lock_camera_rotation_on)
} else {
EMPTY_SECONDARY_STRING
}
- this.iconRes = R.drawable.qs_auto_rotate_icon_on
- }
- this.icon = {
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
+ iconRes = R.drawable.qs_auto_rotate_icon_on
}
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
if (isDeviceFoldable(resources, deviceStateManager)) {
- this.secondaryLabel = getSecondaryLabelWithPosture(this.activationState)
+ secondaryLabel = getSecondaryLabelWithPosture(activationState)
}
- this.stateDescription = this.secondaryLabel
- this.sideViewIcon = QSTileState.SideViewIcon.None
+ stateDescription = secondaryLabel
+ sideViewIcon = QSTileState.SideViewIcon.None
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
}
@@ -86,7 +83,7 @@ constructor(
return resources.getString(
R.string.rotation_tile_with_posture_secondary_label_template,
stateName,
- posture
+ posture,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 888bba87a03a..08196bbfe2f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -29,10 +29,8 @@ import javax.inject.Inject
/** Maps [DataSaverTileModel] to [QSTileState]. */
class DataSaverTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<DataSaverTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<DataSaverTileModel> {
override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
with(data) {
@@ -45,9 +43,7 @@ constructor(
iconRes = R.drawable.qs_data_saver_icon_off
secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
}
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
contentDescription = label
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
index e74e77f29007..ba06de966c10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/screenrecord/domain/ui/ScreenRecordTileMapper.kt
@@ -30,10 +30,8 @@ import javax.inject.Inject
/** Maps [ScreenRecordModel] to [QSTileState]. */
class ScreenRecordTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Resources.Theme,
-) : QSTileDataToStateMapper<ScreenRecordModel> {
+constructor(@Main private val resources: Resources, private val theme: Resources.Theme) :
+ QSTileDataToStateMapper<ScreenRecordModel> {
override fun map(config: QSTileConfig, data: ScreenRecordModel): QSTileState =
QSTileState.build(resources, theme, config.uiConfig) {
label = resources.getString(R.string.quick_settings_screen_record_label)
@@ -43,24 +41,12 @@ constructor(
is ScreenRecordModel.Recording -> {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_screen_record_icon_on
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = resources.getString(R.string.quick_settings_screen_record_stop)
}
is ScreenRecordModel.Starting -> {
activationState = QSTileState.ActivationState.ACTIVE
iconRes = R.drawable.qs_screen_record_icon_on
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
val countDown = data.countdownSeconds
sideViewIcon = QSTileState.SideViewIcon.None
secondaryLabel = String.format("%d...", countDown)
@@ -68,17 +54,13 @@ constructor(
is ScreenRecordModel.DoingNothing -> {
activationState = QSTileState.ActivationState.INACTIVE
iconRes = R.drawable.qs_screen_record_icon_off
- val loadedIcon =
- Icon.Loaded(
- resources.getDrawable(iconRes!!, theme),
- contentDescription = null
- )
- icon = { loadedIcon }
sideViewIcon = QSTileState.SideViewIcon.Chevron // tapping will open dialog
secondaryLabel =
resources.getString(R.string.quick_settings_screen_record_start)
}
}
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
+
contentDescription =
if (TextUtils.isEmpty(secondaryLabel)) label
else TextUtils.concat(label, ", ", secondaryLabel)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
index 597cf274dcff..b4cfec48fb0a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt
@@ -51,8 +51,7 @@ constructor(
supportedActions =
setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
iconRes = sensorPrivacyTileResources.getIconRes(data.isBlocked)
- icon = { Icon.Loaded(resources.getDrawable(iconRes!!, theme), null) }
-
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
sideViewIcon = QSTileState.SideViewIcon.None
if (data.isBlocked) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index f29c745d8119..eda8e5ce8c43 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -34,14 +34,13 @@ import javax.inject.Inject
/** Maps [UiModeNightTileModel] to [QSTileState]. */
class UiModeNightTileMapper
@Inject
-constructor(
- @Main private val resources: Resources,
- private val theme: Theme,
-) : QSTileDataToStateMapper<UiModeNightTileModel> {
+constructor(@Main private val resources: Resources, private val theme: Theme) :
+ QSTileDataToStateMapper<UiModeNightTileModel> {
companion object {
val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
}
+
override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
with(data) {
QSTileState.build(resources, theme, config.uiConfig) {
@@ -76,7 +75,7 @@ constructor(
if (isNightMode)
R.string.quick_settings_dark_mode_secondary_label_until
else R.string.quick_settings_dark_mode_secondary_label_on_at,
- formatter.format(time)
+ formatter.format(time),
)
} else if (
nightModeCustomType == UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME
@@ -121,9 +120,7 @@ constructor(
if (activationState == QSTileState.ActivationState.ACTIVE)
R.drawable.qs_light_dark_theme_icon_on
else R.drawable.qs_light_dark_theme_icon_off
- val loadedIcon =
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- icon = { loadedIcon }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), null)
supportedActions =
if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
index eee95b7311d3..a1bc8a889a1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/work/ui/WorkModeTileMapper.kt
@@ -42,9 +42,7 @@ constructor(
label = getTileLabel()!!
contentDescription = label
iconRes = com.android.internal.R.drawable.stat_sys_managed_profile_status
- icon = {
- Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
- }
+ icon = Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null)
when (data) {
is WorkModeTileModel.HasActiveProfile -> {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 549f0a73908d..8394be5e0a38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -35,7 +35,7 @@ import kotlin.reflect.KClass
* // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
*/
data class QSTileState(
- val icon: () -> Icon?,
+ val icon: Icon?,
val iconRes: Int?,
val label: CharSequence,
val activationState: ActivationState,
@@ -54,21 +54,18 @@ data class QSTileState(
resources: Resources,
theme: Theme,
config: QSTileUIConfig,
- builder: Builder.() -> Unit
+ builder: Builder.() -> Unit,
): QSTileState {
val iconDrawable = resources.getDrawable(config.iconRes, theme)
return build(
- { Icon.Loaded(iconDrawable, null) },
+ Icon.Loaded(iconDrawable, null),
resources.getString(config.labelRes),
builder,
)
}
- fun build(
- icon: () -> Icon?,
- label: CharSequence,
- builder: Builder.() -> Unit
- ): QSTileState = Builder(icon, label).apply { builder() }.build()
+ fun build(icon: Icon?, label: CharSequence, builder: Builder.() -> Unit): QSTileState =
+ Builder(icon, label).apply { builder() }.build()
}
enum class ActivationState(val legacyState: Int) {
@@ -117,10 +114,7 @@ data class QSTileState(
data object None : SideViewIcon
}
- class Builder(
- var icon: () -> Icon?,
- var label: CharSequence,
- ) {
+ class Builder(var icon: Icon?, var label: CharSequence) {
var iconRes: Int? = null
var activationState: ActivationState = ActivationState.INACTIVE
var secondaryLabel: CharSequence? = null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index f89745f49cc8..35b1b9636263 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.viewmodel
import android.content.Context
import android.os.UserHandle
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.InstanceId
import com.android.systemui.Dumpable
import com.android.systemui.animation.Expandable
@@ -42,7 +43,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.takeWhile
-import com.android.app.tracing.coroutines.launchTraced as launch
// TODO(b/http://b/299909989): Use QSTileViewModel directly after the rollout
class QSTileViewModelAdapter
@@ -223,7 +223,7 @@ constructor(
fun mapState(
context: Context,
viewModelState: QSTileState,
- config: QSTileConfig
+ config: QSTileConfig,
): QSTile.State =
// we have to use QSTile.BooleanState to support different side icons
// which are bound to instanceof QSTile.BooleanState in QSTileView.
@@ -241,7 +241,7 @@ constructor(
viewModelState.supportedActions.contains(QSTileState.UserAction.TOGGLE_CLICK)
icon =
- when (val stateIcon = viewModelState.icon()) {
+ when (val stateIcon = viewModelState.icon) {
is Icon.Loaded ->
if (viewModelState.iconRes == null) DrawableIcon(stateIcon.drawable)
else DrawableIconWithRes(stateIcon.drawable, viewModelState.iconRes)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
index 6d5bf328d00b..e922e09c26ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/adapter/QSSceneAdapter.kt
@@ -22,6 +22,7 @@ import android.os.Bundle
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.asynclayoutinflater.view.AsyncLayoutInflater
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.settingslib.applications.InterestingConfigChanges
import com.android.systemui.Dumpable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -57,7 +58,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
// TODO(307945185) Split View concerns into a ViewBinder
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index f3c6190d2f7b..580a51a3dc0a 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -46,7 +46,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
import com.android.systemui.model.SceneContainerPlugin
import com.android.systemui.model.SysUiState
import com.android.systemui.model.updateFlags
@@ -97,7 +96,6 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -138,7 +136,6 @@ constructor(
private val uiEventLogger: UiEventLogger,
private val sceneBackInteractor: SceneBackInteractor,
private val shadeSessionStorage: SessionStorage,
- private val windowMgrLockscreenVisInteractor: WindowManagerLockscreenVisibilityInteractor,
private val keyguardEnabledInteractor: KeyguardEnabledInteractor,
private val dismissCallbackRegistry: DismissCallbackRegistry,
private val statusBarStateController: SysuiStatusBarStateController,
@@ -213,7 +210,6 @@ constructor(
/** Updates the visibility of the scene container. */
private fun hydrateVisibility() {
applicationScope.launch {
- // TODO(b/296114544): Combine with some global hun state to make it visible!
deviceProvisioningInteractor.isDeviceProvisioned
.flatMapLatest { isAllowedToBeVisible ->
if (isAllowedToBeVisible) {
@@ -271,27 +267,6 @@ constructor(
handleDeviceUnlockStatus()
handlePowerState()
handleShadeTouchability()
- handleSurfaceBehindKeyguardVisibility()
- }
-
- private fun handleSurfaceBehindKeyguardVisibility() {
- applicationScope.launch {
- sceneInteractor.currentScene.collectLatest { currentScene ->
- if (currentScene == Scenes.Lockscreen) {
- // Wait for the screen to be on
- powerInteractor.isAwake.first { it }
- // Wait for surface to become visible
- windowMgrLockscreenVisInteractor.surfaceBehindVisibility.first { it }
- // Make sure the device is actually unlocked before force-changing the scene
- deviceUnlockedInteractor.deviceUnlockStatus.first { it.isUnlocked }
- // Override the current transition, if any, by forcing the scene to Gone
- sceneInteractor.changeScene(
- toScene = Scenes.Gone,
- loggingReason = "surface behind keyguard is visible",
- )
- }
- }
- }
}
private fun handleBouncerImeVisibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
index 2048b7c0c142..137b4fdf89bc 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/data/model/DisplayContentModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.screenshot.data.model
import android.app.ActivityTaskManager.RootTaskInfo
+import com.android.systemui.screenshot.policy.childTasksTopDown
/** Information about the tasks on a display. */
data class DisplayContentModel(
@@ -27,3 +28,5 @@ data class DisplayContentModel(
/** A list of root tasks on the display, ordered from top to bottom along the z-axis */
val rootTasks: List<RootTaskInfo>,
)
+
+fun DisplayContentModel.allTasks() = rootTasks.asSequence().flatMap { it.childTasksTopDown() }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
index 5e2b57651de7..2a4fe3ebfb92 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureParameters.kt
@@ -16,15 +16,17 @@
package com.android.systemui.screenshot.policy
-import android.content.ComponentName
import android.os.UserHandle
-/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
data class CaptureParameters(
- /** How should the content be captured? */
+ /** Describes how the image should be obtained. */
val type: CaptureType,
- /** The focused or top component at the time of the screenshot. */
- val component: ComponentName?,
- /** Which user should receive the screenshot file? */
+ /** Which user to receive the image. */
val owner: UserHandle,
+ /**
+ * The task which represents the main content or focal point of the screenshot. This is the task
+ * used for retrieval of [AssistContent][android.app.assist.AssistContent] as well as
+ * [Scroll Capture][android.view.IWindowManager.requestScrollCapture].
+ */
+ val contentTask: TaskReference,
)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
index 0fb536636f1c..73ff566b7306 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CapturePolicy.kt
@@ -22,7 +22,7 @@ import com.android.systemui.screenshot.data.model.DisplayContentModel
fun interface CapturePolicy {
/**
* Test the policy against the current display task state. If the policy applies, Returns a
- * [PolicyResult.Matched] containing [CaptureParameters] used to alter the request.
+ * [PolicyResult.Matched] containing [LegacyCaptureParameters] used to alter the request.
*/
suspend fun check(content: DisplayContentModel): PolicyResult
@@ -35,7 +35,7 @@ fun interface CapturePolicy {
/** Why the policy matched. */
val reason: String,
/** Details on how to modify the screen capture request. */
- val parameters: CaptureParameters,
+ val parameters: LegacyCaptureParameters,
) : PolicyResult
/** The policy rules do not match the given display content and do not apply. */
@@ -43,7 +43,7 @@ fun interface CapturePolicy {
/** The name of the policy rule which matched. */
val policy: String,
/** Why the policy did not match. */
- val reason: String
+ val reason: String,
) : PolicyResult
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
index 945520126474..34c851100d7f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/CaptureType.kt
@@ -20,12 +20,10 @@ import android.graphics.Rect
/** What to capture */
sealed interface CaptureType {
+
/** Capture the entire screen contents. */
data class FullScreen(val displayId: Int) : CaptureType
/** Capture the contents of the task only. */
data class IsolatedTask(val taskId: Int, val taskBounds: Rect?) : CaptureType
-
- data class RootTask(val parentTaskId: Int, val taskBounds: Rect?, val childTaskIds: List<Int>) :
- CaptureType
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
new file mode 100644
index 000000000000..4b697b201cee
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/LegacyCaptureParameters.kt
@@ -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.systemui.screenshot.policy
+
+import android.content.ComponentName
+import android.os.UserHandle
+
+/** The parameters dictated by a [CapturePolicy], used to adjust alter screenshot request. */
+data class LegacyCaptureParameters(
+ /** How should the content be captured? */
+ val type: CaptureType,
+ /** The focused or top component at the time of the screenshot. */
+ val component: ComponentName?,
+ /** Which user should receive the screenshot file? */
+ val owner: UserHandle,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
index e840668688a0..a84cdf40ca69 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt
@@ -80,6 +80,7 @@ class PolicyRequestProcessor(
Log.i(TAG, "$result")
return modify(original, result.parameters)
}
+
is NotMatched -> Log.i(TAG, "$result")
}
}
@@ -89,7 +90,45 @@ class PolicyRequestProcessor(
}
/** Produce a new [ScreenshotData] using [CaptureParameters] */
- suspend fun modify(original: ScreenshotData, updates: CaptureParameters): ScreenshotData {
+ suspend fun modify(original: ScreenshotData, params: CaptureParameters): ScreenshotData {
+ Log.d(TAG, "[modify] CaptureParameters = $params")
+ // Update and apply bitmap capture depending on the parameters.
+ when (val type = params.type) {
+ is IsolatedTask -> {
+ Log.i(TAG, "Capturing task snapshot: $params")
+
+ val taskSnapshot =
+ capture.captureTask(type.taskId) ?: error("Failed to capture task")
+
+ return original.copy(
+ type = TAKE_SCREENSHOT_PROVIDED_IMAGE,
+ bitmap = taskSnapshot,
+ userHandle = params.owner,
+ taskId = params.contentTask.taskId,
+ topComponent = params.contentTask.component,
+ originalScreenBounds = type.taskBounds,
+ )
+ }
+
+ is FullScreen -> {
+ Log.i(TAG, "Capturing screenshot: $params")
+
+ val screenshot =
+ captureDisplay(type.displayId) ?: error("Failed to capture screenshot")
+ return original.copy(
+ type = TAKE_SCREENSHOT_FULLSCREEN,
+ bitmap = screenshot,
+ userHandle = params.owner,
+ topComponent = params.contentTask.component,
+ originalScreenBounds = Rect(0, 0, screenshot.width, screenshot.height),
+ taskId = params.contentTask.taskId,
+ )
+ }
+ }
+ }
+
+ /** Produce a new [ScreenshotData] using [LegacyCaptureParameters] */
+ suspend fun modify(original: ScreenshotData, updates: LegacyCaptureParameters): ScreenshotData {
Log.d(TAG, "[modify] CaptureParameters = $updates")
// Update and apply bitmap capture depending on the parameters.
val updated =
@@ -102,14 +141,7 @@ class PolicyRequestProcessor(
type.taskId,
type.taskBounds,
)
- is CaptureType.RootTask ->
- replaceWithTaskSnapshot(
- original,
- updates.component,
- updates.owner,
- type.parentTaskId,
- type.taskBounds,
- )
+
is FullScreen ->
replaceWithScreenshot(
original,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
index 1945c2575655..3857ba305ad0 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PrivateProfilePolicy.kt
@@ -31,11 +31,8 @@ import javax.inject.Inject
*
* Parameters: Capture the whole screen, owned by the private user.
*/
-class PrivateProfilePolicy
-@Inject
-constructor(
- private val profileTypes: ProfileTypeRepository,
-) : CapturePolicy {
+class PrivateProfilePolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) :
+ CapturePolicy {
override suspend fun check(content: DisplayContentModel): PolicyResult {
// The systemUI notification shade isn't a private profile app, skip.
if (content.systemUiState.shadeExpanded) {
@@ -47,25 +44,23 @@ constructor(
content.rootTasks
.filter { it.isVisible }
.firstNotNullOfOrNull { root ->
- root
- .childTasksTopDown()
- .firstOrNull {
- profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
- }
- }
- ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
+ root.childTasksTopDown().firstOrNull {
+ profileTypes.getProfileType(it.userId) == ProfileType.PRIVATE
+ }
+ } ?: return NotMatched(policy = NAME, reason = NO_VISIBLE_TASKS)
// If matched, return parameters needed to modify the request.
return Matched(
policy = NAME,
reason = PRIVATE_TASK_VISIBLE,
- CaptureParameters(
+ LegacyCaptureParameters(
type = FullScreen(content.displayId),
component = content.rootTasks.first { it.isVisible }.topActivity,
owner = UserHandle.of(childTask.userId),
- )
+ ),
)
}
+
companion object {
const val NAME = "PrivateProfile"
const val SHADE_EXPANDED = "Notification shade is expanded"
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
index dd39f92643ce..c43e929295e1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/RootTaskInfoExt.kt
@@ -20,7 +20,7 @@ import android.app.ActivityTaskManager.RootTaskInfo
import com.android.systemui.screenshot.data.model.ChildTaskModel
/** The child tasks of A RootTaskInfo as [ChildTaskModel] in top-down (z-index ascending) order. */
-internal fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
+fun RootTaskInfo.childTasksTopDown(): Sequence<ChildTaskModel> {
return ((childTaskIds.size - 1) downTo 0).asSequence().map { index ->
ChildTaskModel(
childTaskIds[index],
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
index 9967afffb6a0..5213579d5ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/ScreenshotPolicy.kt
@@ -20,18 +20,16 @@ import android.app.ActivityTaskManager.RootTaskInfo
import android.app.WindowConfiguration
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_PINNED
import android.content.ComponentName
+import android.graphics.Rect
import android.os.UserHandle
import android.util.Log
import com.android.systemui.screenshot.data.model.DisplayContentModel
-import com.android.systemui.screenshot.data.model.ProfileType
import com.android.systemui.screenshot.data.model.ProfileType.PRIVATE
import com.android.systemui.screenshot.data.model.ProfileType.WORK
import com.android.systemui.screenshot.data.repository.ProfileTypeRepository
import com.android.systemui.screenshot.policy.CaptureType.FullScreen
import com.android.systemui.screenshot.policy.CaptureType.IsolatedTask
-import com.android.systemui.screenshot.policy.CaptureType.RootTask
import javax.inject.Inject
private const val TAG = "ScreenshotPolicy"
@@ -39,7 +37,7 @@ private const val TAG = "ScreenshotPolicy"
/** Determines what to capture and which user owns the output. */
class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileTypeRepository) {
/**
- * Apply the policy to the content, resulting in [CaptureParameters].
+ * Apply the policy to the content, resulting in [LegacyCaptureParameters].
*
* @param content the content of the display
* @param defaultComponent the component associated with the screenshot by default
@@ -53,7 +51,7 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType
val defaultFullScreen by lazy {
CaptureParameters(
type = FullScreen(displayId = content.displayId),
- component = defaultComponent,
+ contentTask = TaskReference(-1, defaultComponent, defaultOwner, Rect()),
owner = defaultOwner,
)
}
@@ -70,32 +68,47 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType
} ?: return defaultFullScreen
Log.d(TAG, "topRootTask: $topRootTask")
- val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
- // Special case: Only WORK in top root task which is full-screen or maximized freeform
+ // When:
+ // * there is one or more child task
+ // * all owned by the same user
+ // * this user is a work profile
+ // * the root task is fullscreen or freeform-maximized
+ //
+ // Then:
+ // the result will be a task snapshot instead of a full screen capture. If there is more
+ // than one child task, the root task will be snapshot to include any/all child tasks. This
+ // is intended to cover split-screen mode.
+ val rootTaskOwners = topRootTask.childTaskUserIds.distinct()
if (
rootTaskOwners.size == 1 &&
profileTypes.getProfileType(rootTaskOwners.single()) == WORK &&
(topRootTask.isFullScreen() || topRootTask.isMaximizedFreeform())
) {
+ val topChildTask = topRootTask.childTasksTopDown().first()
+
+ // If there is more than one task, capture the parent to include both.
val type =
if (topRootTask.childTaskCount() > 1) {
- RootTask(
- parentTaskId = topRootTask.taskId,
- taskBounds = topRootTask.bounds,
- childTaskIds = topRootTask.childTasksTopDown().map { it.id }.toList(),
- )
+ IsolatedTask(taskId = topRootTask.taskId, taskBounds = topRootTask.bounds)
} else {
- IsolatedTask(
- taskId = topRootTask.childTasksTopDown().first().id,
- taskBounds = topRootTask.bounds,
- )
+ // Otherwise capture the single task, and use its bounds.
+ IsolatedTask(taskId = topChildTask.id, taskBounds = topChildTask.bounds)
}
- // Capture the RootTask (and all children)
+
+ // The content task (the focus of the screenshot) must represent a single task
+ // containing an activity, so always reference the top child task here. The owner
+ // of the screenshot here is always the same as well.
return CaptureParameters(
type = type,
- component = topRootTask.topActivity,
- owner = UserHandle.of(rootTaskOwners.single()),
+ contentTask =
+ TaskReference(
+ taskId = topChildTask.id,
+ component = topRootTask.topActivity ?: defaultComponent,
+ owner = UserHandle.of(topChildTask.userId),
+ bounds = topChildTask.bounds,
+ ),
+ owner = UserHandle.of(topChildTask.userId),
)
}
@@ -105,26 +118,36 @@ class ScreenshotPolicy @Inject constructor(private val profileTypes: ProfileType
val visibleChildTasks =
content.rootTasks.filter { it.isVisible }.flatMap { it.childTasksTopDown() }
+ // Don't target a PIP window as the screenshot "content", it should only be used
+ // to determine ownership (above).
+ val contentTask =
+ content.rootTasks
+ .filter {
+ it.isVisible && it.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
+ }
+ .flatMap { it.childTasksTopDown() }
+ .first()
+
val allVisibleProfileTypes =
visibleChildTasks
.map { it.userId }
.distinct()
.associate { profileTypes.getProfileType(it) to UserHandle.of(it) }
- // If any visible content belongs to the private profile user -> private profile
- // otherwise the personal user (including partial screen work content).
- val ownerHandle =
- allVisibleProfileTypes[PRIVATE]
- ?: allVisibleProfileTypes[ProfileType.NONE]
- ?: defaultOwner
-
- // Attribute to the component of top-most task owned by this user (or fallback to default)
- val topComponent =
- visibleChildTasks.firstOrNull { it.userId == ownerHandle.identifier }?.componentName
+ // If any task is visible and owned by a PRIVATE profile user, the screenshot is assigned
+ // to that user. Work profile has been handled above so it is not considered here. Fallback
+ // to the default user which is the primary "current" user ('aka' personal "profile").
+ val ownerHandle = allVisibleProfileTypes[PRIVATE] ?: defaultOwner
return CaptureParameters(
type = FullScreen(content.displayId),
- component = topComponent ?: topRootTask.topActivity ?: defaultComponent,
+ contentTask =
+ TaskReference(
+ taskId = contentTask.id,
+ component = contentTask.componentName,
+ owner = UserHandle.of(contentTask.userId),
+ bounds = contentTask.bounds,
+ ),
owner = ownerHandle,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
new file mode 100644
index 000000000000..04f5b1ef2885
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/TaskReference.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.screenshot.policy
+
+import android.content.ComponentName
+import android.graphics.Rect
+import android.os.UserHandle
+
+data class TaskReference(
+ /** The id of the task. */
+ val taskId: Int,
+ /** The component name of the task. */
+ val component: ComponentName?,
+ /** The owner of the task. */
+ val owner: UserHandle,
+ /** The bounds of the task. */
+ val bounds: Rect,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
index cf90c0a58e94..109c1cbf7e8e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/WorkProfilePolicy.kt
@@ -68,7 +68,7 @@ constructor(private val profileTypes: ProfileTypeRepository, private val context
return PolicyResult.Matched(
policy = NAME,
reason = WORK_TASK_IS_TOP,
- CaptureParameters(
+ LegacyCaptureParameters(
type = IsolatedTask(taskId = childTask.id, taskBounds = childTask.bounds),
component = childTask.componentName ?: rootTask.topActivity,
owner = UserHandle.of(childTask.userId),
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
new file mode 100644
index 000000000000..3d7b2ea2bc6e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserSettingsRepositoryModule.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.settings
+
+import android.content.ContentResolver
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
+import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SystemSettings
+import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+
+@Module
+object UserSettingsRepositoryModule {
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSecureSettingsRepository(
+ secureSettings: Lazy<SecureSettings>,
+ userRepository: Lazy<UserRepository>,
+ contentResolver: Lazy<ContentResolver>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background backgroundContext: CoroutineContext,
+ ): SecureSettingsRepository {
+ return if (Flags.userAwareSettingsRepositories()) {
+ UserAwareSecureSettingsRepository(
+ secureSettings.get(),
+ userRepository.get(),
+ backgroundDispatcher,
+ backgroundContext,
+ )
+ } else {
+ SecureSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+ }
+ }
+
+ @JvmStatic
+ @Provides
+ @SysUISingleton
+ fun provideSystemSettingsRepository(
+ systemSettings: Lazy<SystemSettings>,
+ userRepository: Lazy<UserRepository>,
+ contentResolver: Lazy<ContentResolver>,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background backgroundContext: CoroutineContext,
+ ): SystemSettingsRepository {
+ return if (Flags.userAwareSettingsRepositories()) {
+ UserAwareSystemSettingsRepository(
+ systemSettings.get(),
+ userRepository.get(),
+ backgroundDispatcher,
+ backgroundContext,
+ )
+ } else {
+ SystemSettingsRepositoryImpl(contentResolver.get(), backgroundDispatcher)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index 6e63446d88d8..1776a2c4a439 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -18,14 +18,14 @@ package com.android.systemui.shade
import android.content.Context
import android.view.ViewGroup
-import com.android.systemui.res.R
import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.statusbar.StatusBarState.SHADE
-import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.res.R
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
import com.android.systemui.unfold.SysUIUnfoldScope
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import javax.inject.Inject
@@ -39,8 +39,10 @@ constructor(
progressProvider: NaturalRotationUnfoldProgressProvider,
) {
- private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
- statusBarStateController.getState() == SHADE_LOCKED }
+ private val filterShade: () -> Boolean = {
+ statusBarStateController.getState() == SHADE ||
+ statusBarStateController.getState() == SHADE_LOCKED
+ }
private val translateAnimator by lazy {
UnfoldConstantTranslateAnimator(
@@ -48,21 +50,23 @@ constructor(
setOf(
ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
ViewIdToTranslate(R.id.qs_footer_actions, START, filterShade),
- ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade)),
- progressProvider = progressProvider)
+ ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+ ),
+ progressProvider = progressProvider,
+ )
}
private val translateAnimatorStatusBar by lazy {
UnfoldConstantTranslateAnimator(
viewsIdToTranslate =
- setOf(
- ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
- ViewIdToTranslate(R.id.privacy_container, END, filterShade),
- ViewIdToTranslate(R.id.carrier_group, END, filterShade),
- ViewIdToTranslate(R.id.clock, START, filterShade),
- ViewIdToTranslate(R.id.date, START, filterShade)
- ),
- progressProvider = progressProvider
+ setOf(
+ ViewIdToTranslate(R.id.shade_header_system_icons, END, filterShade),
+ ViewIdToTranslate(R.id.privacy_container, END, filterShade),
+ ViewIdToTranslate(R.id.carrier_group, END, filterShade),
+ ViewIdToTranslate(R.id.clock, START, filterShade),
+ ViewIdToTranslate(R.id.date, START, filterShade),
+ ),
+ progressProvider = progressProvider,
)
}
@@ -73,10 +77,7 @@ constructor(
val splitShadeStatusBarViewGroup: ViewGroup? =
root.findViewById(R.id.split_shade_status_bar)
if (splitShadeStatusBarViewGroup != null) {
- translateAnimatorStatusBar.init(
- splitShadeStatusBarViewGroup,
- translationMax
- )
+ translateAnimatorStatusBar.init(splitShadeStatusBarViewGroup, translationMax)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 0e82bf82fdf9..c15c8f946855 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2053,6 +2053,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
if (mQsController.getExpanded()) {
mQsController.flingQs(0, FLING_COLLAPSE);
+ } else if (mBarState == KEYGUARD) {
+ mLockscreenShadeTransitionController.goToLockedShade(
+ /* expandedView= */null, /* needsQSAnimation= */false);
} else {
expand(true /* animate */);
}
@@ -3109,7 +3112,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
if (isTracking()) {
onTrackingStopped(true);
}
- if (isExpanded() && !mQsController.getExpanded()) {
+ if (isExpanded() && mBarState != KEYGUARD && !mQsController.getExpanded()) {
mShadeLog.d("Status Bar was long pressed. Expanding to QS.");
expandToQs();
} else {
@@ -5091,13 +5094,6 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
boolean handled = mHeadsUpTouchHelper.onTouchEvent(event);
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
- event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
- if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
- mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
- }
- return true;
- }
// This touch session has already resulted in shade expansion. Ignore everything else.
if (ShadeExpandsOnStatusBarLongPress.isEnabled()
&& event.getActionMasked() != MotionEvent.ACTION_DOWN
@@ -5105,6 +5101,13 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
mShadeLog.d("Touch has same down time as Status Bar long press. Ignoring.");
return false;
}
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && mQsController.handleTouch(
+ event, isFullyCollapsed(), isShadeOrQsHeightAnimationRunning())) {
+ if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mShadeLog.logMotionEvent(event, "onTouch: handleQsTouch handled event");
+ }
+ return true;
+ }
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
handled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index a8026cd61aaf..6fb9b1fe8873 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,7 +20,11 @@ import android.content.Context
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.common.ui.ConfigurationState
+import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.common.ui.GlobalConfig
+import com.android.systemui.common.ui.data.repository.ConfigurationRepository
+import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
@@ -92,9 +96,41 @@ object ShadeDisplayAwareModule {
@ShadeDisplayAware
@SysUISingleton
fun provideShadeWindowConfigurationForwarder(
- @ShadeDisplayAware shadeConfigurationController: ConfigurationController,
+ @ShadeDisplayAware shadeConfigurationController: ConfigurationController
): ConfigurationForwarder {
ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
return shadeConfigurationController
}
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationState(
+ factory: ConfigurationStateImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig configurationState: ConfigurationState,
+ ): ConfigurationState {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(context, configurationController)
+ } else {
+ configurationState
+ }
+ }
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadeDisplayAwareConfigurationRepository(
+ factory: ConfigurationRepositoryImpl.Factory,
+ @ShadeDisplayAware configurationController: ConfigurationController,
+ @ShadeDisplayAware context: Context,
+ @GlobalConfig globalConfigurationRepository: ConfigurationRepository,
+ ): ConfigurationRepository {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ factory.create(context, configurationController)
+ } else {
+ globalConfigurationRepository
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
index 72a465030bf5..2348a110eb3a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeModule.kt
@@ -49,10 +49,7 @@ import dagger.Provides
import javax.inject.Provider
/** Module for classes related to the notification shade. */
-@Module(
- includes =
- [StartShadeModule::class, ShadeViewProviderModule::class]
-)
+@Module(includes = [StartShadeModule::class, ShadeViewProviderModule::class])
abstract class ShadeModule {
companion object {
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
index 6fb3ca5f86d2..ae36e81c7b1f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LongPressGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/StatusBarLongPressGestureDetector.kt
@@ -25,7 +25,7 @@ import javax.inject.Inject
/** Accepts touch events, detects long press, and calls ShadeViewController#onStatusBarLongPress. */
@SysUISingleton
-class LongPressGestureDetector
+class StatusBarLongPressGestureDetector
@Inject
constructor(context: Context, val shadeViewController: ShadeViewController) {
val gestureDetector =
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 5629938deeb0..ef62d2da9589 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -15,9 +15,7 @@
*/
package com.android.systemui.shade.data.repository
-import android.content.Context
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -182,8 +180,7 @@ interface ShadeRepository {
/** Business logic for shade interactions */
@SysUISingleton
-class ShadeRepositoryImpl @Inject constructor() :
- ShadeRepository {
+class ShadeRepositoryImpl @Inject constructor() : ShadeRepository {
private val _qsExpansion = MutableStateFlow(0f)
@Deprecated("Use ShadeInteractor.qsExpansion instead")
override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index e5d08a0ac977..44f29119a1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.startable
import android.content.Context
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.CoreStartable
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -42,7 +43,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onStart
-import com.android.app.tracing.coroutines.launchTraced as launch
@SysUISingleton
class ShadeStartable
@@ -51,7 +51,7 @@ constructor(
@Application private val applicationScope: CoroutineScope,
@ShadeDisplayAware private val context: Context,
@ShadeTouchLog private val touchLog: LogBuffer,
- private val configurationRepository: ConfigurationRepository,
+ @ShadeDisplayAware private val configurationRepository: ConfigurationRepository,
private val shadeRepository: ShadeRepository,
private val splitShadeStateController: SplitShadeStateController,
private val scrimShadeTransitionController: ScrimShadeTransitionController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 520cbf9d80d9..8c5a711d6a75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -619,10 +619,11 @@ public class KeyguardIndicationController {
}
private void updateLockScreenUserLockedMsg(int userId) {
- boolean userUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId);
+ boolean userStorageUnlocked = mKeyguardUpdateMonitor.isUserUnlocked(userId);
boolean encryptedOrLockdown = mKeyguardUpdateMonitor.isEncryptedOrLockdown(userId);
- mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userUnlocked, encryptedOrLockdown);
- if (!userUnlocked || encryptedOrLockdown) {
+ mKeyguardLogger.logUpdateLockScreenUserLockedMsg(userId, userStorageUnlocked,
+ encryptedOrLockdown);
+ if (!userStorageUnlocked || encryptedOrLockdown) {
mRotateTextViewController.updateIndication(
INDICATION_TYPE_USER_LOCKED,
new KeyguardIndication.Builder()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index 3abbc6e0d1cb..f441fd644c17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -23,6 +23,7 @@ import com.android.systemui.fragments.FragmentHostManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions
import com.android.systemui.statusbar.phone.PhoneStatusBarView
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
@@ -71,7 +72,10 @@ interface StatusBarInitializer : CoreStartable {
}
interface Factory {
- fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer
+ fun create(
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
+ ): StatusBarInitializer
}
}
@@ -79,6 +83,7 @@ class StatusBarInitializerImpl
@AssistedInject
constructor(
@Assisted private val statusBarWindowController: StatusBarWindowController,
+ @Assisted private val statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>,
private val statusBarRootFactory: StatusBarRootFactory,
private val componentFactory: HomeStatusBarComponent.Factory,
@@ -127,7 +132,7 @@ constructor(
val phoneStatusBarView = cv.findViewById<PhoneStatusBarView>(R.id.status_bar)
component =
componentFactory.create(phoneStatusBarView).also { component ->
- // CollapsedStatusBarFragment used to be responsible initializting
+ // CollapsedStatusBarFragment used to be responsible initializing
component.init()
statusBarViewUpdatedListener?.onStatusBarViewUpdated(
@@ -135,8 +140,12 @@ constructor(
component.phoneStatusBarTransitions,
)
- creationListeners.forEach { listener ->
- listener.onStatusBarViewInitialized(component)
+ if (StatusBarConnectedDisplays.isEnabled) {
+ statusBarModePerDisplayRepository.onStatusBarViewInitialized(component)
+ } else {
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(component)
+ }
}
}
}
@@ -184,7 +193,8 @@ constructor(
@AssistedFactory
interface Factory : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializerImpl
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
index 041f0b0fdf93..4f815c1f0b31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt
@@ -22,6 +22,7 @@ import com.android.systemui.display.data.repository.DisplayRepository
import com.android.systemui.display.data.repository.PerDisplayStore
import com.android.systemui.display.data.repository.PerDisplayStoreImpl
import com.android.systemui.display.data.repository.SingleDisplayStore
+import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -37,6 +38,7 @@ constructor(
displayRepository: DisplayRepository,
private val factory: StatusBarInitializer.Factory,
private val statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
) :
StatusBarInitializerStore,
PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) {
@@ -47,7 +49,8 @@ constructor(
override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer {
return factory.create(
- statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId)
+ statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId),
+ statusBarModePerDisplayRepository = statusBarModeRepositoryStore.forDisplay(displayId),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
index f65ae67efbf1..434120051039 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt
@@ -26,6 +26,7 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.data.StatusBarDataLayerModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStore
import com.android.systemui.statusbar.phone.LightBarController
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl
@@ -55,26 +56,26 @@ import dagger.multibindings.IntoMap
* [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.).
*/
@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class])
-abstract class StatusBarModule {
+interface StatusBarModule {
@Binds
@IntoMap
@ClassKey(OngoingCallController::class)
- abstract fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
+ fun bindOngoingCallController(impl: OngoingCallController): CoreStartable
@Binds
@IntoMap
@ClassKey(LightBarController::class)
- abstract fun bindLightBarController(impl: LightBarController): CoreStartable
+ fun lightBarControllerAsCoreStartable(controller: LightBarController): CoreStartable
@Binds
@IntoMap
@ClassKey(StatusBarSignalPolicy::class)
- abstract fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
+ fun bindStatusBarSignalPolicy(impl: StatusBarSignalPolicy): CoreStartable
@Binds
@SysUISingleton
- abstract fun statusBarWindowControllerFactory(
+ fun statusBarWindowControllerFactory(
implFactory: StatusBarWindowControllerImpl.Factory
): StatusBarWindowController.Factory
@@ -82,6 +83,12 @@ abstract class StatusBarModule {
@Provides
@SysUISingleton
+ fun lightBarController(store: LightBarControllerStore): LightBarController {
+ return store.defaultDisplay
+ }
+
+ @Provides
+ @SysUISingleton
fun windowControllerStore(
multiDisplayImplLazy: Lazy<MultiDisplayStatusBarWindowControllerStore>,
singleDisplayImplLazy: Lazy<SingleDisplayStatusBarWindowControllerStore>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
index f2d926fc22b1..39de28e7cb49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/StatusBarDataLayerModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.data
import com.android.systemui.statusbar.data.repository.KeyguardStatusBarRepositoryModule
+import com.android.systemui.statusbar.data.repository.LightBarControllerStoreModule
import com.android.systemui.statusbar.data.repository.RemoteInputRepositoryModule
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationControllerModule
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStoreModule
@@ -28,6 +29,7 @@ import dagger.Module
includes =
[
KeyguardStatusBarRepositoryModule::class,
+ LightBarControllerStoreModule::class,
RemoteInputRepositoryModule::class,
StatusBarConfigurationControllerModule::class,
StatusBarContentInsetsProviderStoreModule::class,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
new file mode 100644
index 000000000000..ff50e3100672
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/LightBarControllerStore.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.data.repository
+
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.DisplayScopeRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.phone.LightBarController
+import com.android.systemui.statusbar.phone.LightBarControllerImpl
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+
+/** Provides per display instances of [LightBarController]. */
+interface LightBarControllerStore : PerDisplayStore<LightBarController>
+
+@SysUISingleton
+class LightBarControllerStoreImpl
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ displayRepository: DisplayRepository,
+ private val factory: LightBarControllerImpl.Factory,
+ private val displayScopeRepository: DisplayScopeRepository,
+ private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
+) :
+ LightBarControllerStore,
+ PerDisplayStoreImpl<LightBarController>(backgroundApplicationScope, displayRepository) {
+
+ override fun createInstanceForDisplay(displayId: Int): LightBarController {
+ return factory
+ .create(
+ displayId,
+ displayScopeRepository.scopeForDisplay(displayId),
+ statusBarModeRepositoryStore.forDisplay(displayId),
+ )
+ .also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: LightBarController) {
+ instance.stop()
+ }
+
+ override val instanceClass = LightBarController::class.java
+}
+
+@Module
+interface LightBarControllerStoreModule {
+
+ @Binds fun store(impl: LightBarControllerStoreImpl): LightBarControllerStore
+
+ @Binds
+ @IntoMap
+ @ClassKey(LightBarControllerStore::class)
+ fun storeAsCoreStartable(impl: LightBarControllerStoreImpl): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
index 44bee1d784fc..cc91e2dc3a25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModePerDisplayRepository.kt
@@ -27,7 +27,7 @@ import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BA
import android.view.WindowInsetsController.Appearance
import com.android.internal.statusbar.LetterboxDetails
import com.android.internal.view.AppearanceRegion
-import com.android.systemui.Dumpable
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
@@ -59,7 +59,7 @@ import kotlinx.coroutines.flow.stateIn
* Note: These status bar modes are status bar *window* states that are sent to us from
* WindowManager, not determined internally.
*/
-interface StatusBarModePerDisplayRepository {
+interface StatusBarModePerDisplayRepository : OnStatusBarViewInitializedListener, CoreStartable {
/**
* True if the status bar window is showing transiently and will disappear soon, and false
* otherwise. ("Otherwise" in this case means the status bar is persistently hidden OR
@@ -104,6 +104,12 @@ interface StatusBarModePerDisplayRepository {
* determined internally instead.
*/
fun clearTransient()
+
+ /**
+ * Called when the [StatusBarModePerDisplayRepository] should stop doing any work and clean up
+ * if needed.
+ */
+ fun stop()
}
class StatusBarModePerDisplayRepositoryImpl
@@ -114,7 +120,7 @@ constructor(
private val commandQueue: CommandQueue,
private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
ongoingCallRepository: OngoingCallRepository,
-) : StatusBarModePerDisplayRepository, OnStatusBarViewInitializedListener, Dumpable {
+) : StatusBarModePerDisplayRepository {
private val commandQueueCallback =
object : CommandQueue.Callbacks {
@@ -163,10 +169,14 @@ constructor(
}
}
- fun start() {
+ override fun start() {
commandQueue.addCallback(commandQueueCallback)
}
+ override fun stop() {
+ commandQueue.removeCallback(commandQueueCallback)
+ }
+
private val _isTransientShown = MutableStateFlow(false)
override val isTransientShown: StateFlow<Boolean> = _isTransientShown.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
index 2c9fa25d8535..143e99823860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt
@@ -18,21 +18,54 @@ package com.android.systemui.statusbar.data.repository
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.display.data.repository.DisplayRepository
+import com.android.systemui.display.data.repository.PerDisplayStore
+import com.android.systemui.display.data.repository.PerDisplayStoreImpl
+import com.android.systemui.statusbar.core.StatusBarConnectedDisplays
import com.android.systemui.statusbar.core.StatusBarInitializer
import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
+import dagger.Lazy
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import dagger.multibindings.IntoSet
import java.io.PrintWriter
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
-interface StatusBarModeRepositoryStore {
- val defaultDisplay: StatusBarModePerDisplayRepository
+interface StatusBarModeRepositoryStore : PerDisplayStore<StatusBarModePerDisplayRepository>
- fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository
+@SysUISingleton
+class MultiDisplayStatusBarModeRepositoryStore
+@Inject
+constructor(
+ @Background backgroundApplicationScope: CoroutineScope,
+ private val factory: StatusBarModePerDisplayRepositoryFactory,
+ displayRepository: DisplayRepository,
+) :
+ StatusBarModeRepositoryStore,
+ PerDisplayStoreImpl<StatusBarModePerDisplayRepository>(
+ backgroundApplicationScope,
+ displayRepository,
+ ) {
+
+ init {
+ StatusBarConnectedDisplays.assertInNewMode()
+ }
+
+ override fun createInstanceForDisplay(displayId: Int): StatusBarModePerDisplayRepository {
+ return factory.create(displayId).also { it.start() }
+ }
+
+ override suspend fun onDisplayRemovalAction(instance: StatusBarModePerDisplayRepository) {
+ instance.stop()
+ }
+
+ override val instanceClass = StatusBarModePerDisplayRepository::class.java
}
@SysUISingleton
@@ -47,10 +80,7 @@ constructor(
StatusBarInitializer.OnStatusBarViewInitializedListener {
override val defaultDisplay = factory.create(displayId)
- override fun forDisplay(displayId: Int) =
- // TODO(b/369337087): implement per display status bar modes.
- // For now just use default display instance.
- defaultDisplay
+ override fun forDisplay(displayId: Int) = defaultDisplay
override fun start() {
defaultDisplay.start()
@@ -66,17 +96,40 @@ constructor(
}
@Module
-interface StatusBarModeRepositoryModule {
- @Binds fun bindImpl(impl: StatusBarModeRepositoryImpl): StatusBarModeRepositoryStore
-
- @Binds
- @IntoMap
- @ClassKey(StatusBarModeRepositoryStore::class)
- fun bindCoreStartable(impl: StatusBarModeRepositoryImpl): CoreStartable
-
+abstract class StatusBarModeRepositoryModule {
@Binds
@IntoSet
- fun bindViewInitListener(
+ abstract fun bindViewInitListener(
impl: StatusBarModeRepositoryImpl
): StatusBarInitializer.OnStatusBarViewInitializedListener
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @IntoMap
+ @ClassKey(StatusBarModeRepositoryStore::class)
+ fun storeAsCoreStartable(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): CoreStartable {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+
+ @Provides
+ @SysUISingleton
+ fun store(
+ singleDisplayLazy: Lazy<StatusBarModeRepositoryImpl>,
+ multiDisplayLazy: Lazy<MultiDisplayStatusBarModeRepositoryStore>,
+ ): StatusBarModeRepositoryStore {
+ return if (StatusBarConnectedDisplays.isEnabled) {
+ multiDisplayLazy.get()
+ } else {
+ singleDisplayLazy.get()
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 77ec65bfad6f..9a779300de97 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -49,12 +49,12 @@ class ConversationNotificationProcessor
@Inject
constructor(
private val launcherApps: LauncherApps,
- private val conversationNotificationManager: ConversationNotificationManager
+ private val conversationNotificationManager: ConversationNotificationManager,
) {
fun processNotification(
entry: NotificationEntry,
recoveredBuilder: Notification.Builder,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): Notification.MessagingStyle? {
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
@@ -83,7 +83,7 @@ constructor(
private val notifCollection: CommonNotifCollection,
private val bindEventManager: BindEventManager,
private val headsUpManager: HeadsUpManager,
- private val statusBarStateController: StatusBarStateController
+ private val statusBarStateController: StatusBarStateController,
) {
private var isStatusBarExpanded = false
@@ -118,7 +118,8 @@ constructor(
.flatMap { layout -> layout.allViews.asSequence() }
.flatMap { view ->
(view as? ConversationLayout)?.messagingGroups?.asSequence()
- ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+ ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+ ?: emptySequence()
}
.flatMap { messagingGroup -> messagingGroup.messageContainer.children }
.mapNotNull { view ->
@@ -144,7 +145,7 @@ constructor(
bindEventManager: BindEventManager,
@ShadeDisplayAware private val context: Context,
private val notifCollection: CommonNotifCollection,
- @Main private val mainHandler: Handler
+ @Main private val mainHandler: Handler,
) {
// Need this state to be thread safe, since it's accessed from the ui thread
// (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
@@ -175,7 +176,7 @@ constructor(
// the notif has been moved in the shade
mainHandler.postDelayed(
{ layout.setIsImportantConversation(important, true) },
- IMPORTANCE_ANIMATION_DELAY.toLong()
+ IMPORTANCE_ANIMATION_DELAY.toLong(),
)
} else {
layout.setIsImportantConversation(important, false)
@@ -233,8 +234,7 @@ constructor(
state?.run {
if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
else unreadCount
- }
- ?: 1
+ } ?: 1
ConversationState(newCount, entry.sbn.notification)
}!!
.unreadCount
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index de868d45e64f..df8e56eb4102 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -33,29 +33,30 @@ import javax.inject.Inject
* they are fully attached.
*/
@CoordinatorScope
-class RowAppearanceCoordinator @Inject internal constructor(
+class RowAppearanceCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
- private var mSectionStyleProvider: SectionStyleProvider
+ private var mSectionStyleProvider: SectionStyleProvider,
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
/**
- * `true` if notifications not part of a group should by default be rendered in their
- * expanded state. If `false`, then only the first notification will be expanded if
- * possible.
+ * `true` if notifications not part of a group should by default be rendered in their expanded
+ * state. If `false`, then only the first notification will be expanded if possible.
*/
private val mAlwaysExpandNonGroupedNotification =
context.resources.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications)
/**
- * `true` if the first non-group expandable notification should be expanded automatically
- * when possible. If `false`, then the first non-group expandable notification should not
- * be expanded.
+ * `true` if the first non-group expandable notification should be expanded automatically when
+ * possible. If `false`, then the first non-group expandable notification should not be
+ * expanded.
*/
private val mAutoExpandFirstNotification =
- context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
+ context.resources.getBoolean(R.bool.config_autoExpandFirstNotification)
override fun attach(pipeline: NotifPipeline) {
pipeline.addOnBeforeRenderListListener(::onBeforeRenderList)
@@ -63,17 +64,20 @@ class RowAppearanceCoordinator @Inject internal constructor(
}
private fun onBeforeRenderList(list: List<ListEntry>) {
- entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
- !mSectionStyleProvider.isMinimizedSection(entry.section!!)
- }
+ entryToExpand =
+ list.firstOrNull()?.representativeEntry?.takeIf { entry ->
+ !mSectionStyleProvider.isMinimizedSection(entry.section!!)
+ }
}
private fun onAfterRenderEntry(entry: NotificationEntry, controller: NotifRowController) {
// If mAlwaysExpandNonGroupedNotification is false, then only expand the
// very first notification if it's not a child of grouped notifications and when
// mAutoExpandFirstNotification is true.
- controller.setSystemExpanded(mAlwaysExpandNonGroupedNotification ||
- (mAutoExpandFirstNotification && entry == entryToExpand))
+ controller.setSystemExpanded(
+ mAlwaysExpandNonGroupedNotification ||
+ (mAutoExpandFirstNotification && entry == entryToExpand)
+ )
// Show/hide the feedback icon
controller.setFeedbackIcon(mAssistantFeedbackController.getFeedbackIcon(entry))
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index db778b801dbc..2d1eccdf1abd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -17,10 +17,12 @@
package com.android.systemui.statusbar.notification.collection.coordinator
import android.util.Log
+import com.android.app.tracing.traceSection
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
import com.android.systemui.statusbar.notification.ColorUpdateLogger
@@ -29,17 +31,17 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.Compile
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import javax.inject.Inject
/**
- * A coordinator which ensures that notifications within the new pipeline are correctly inflated
- * for the current uiMode and screen properties; additionally deferring those changes when a user
- * change is in progress until that process has completed.
+ * A coordinator which ensures that notifications within the new pipeline are correctly inflated for
+ * the current uiMode and screen properties; additionally deferring those changes when a user change
+ * is in progress until that process has completed.
*/
@CoordinatorScope
-class ViewConfigCoordinator @Inject internal constructor(
+class ViewConfigCoordinator
+@Inject
+internal constructor(
@ShadeDisplayAware private val mConfigurationController: ConfigurationController,
private val mLockscreenUserManager: NotificationLockscreenUserManager,
private val mGutsManager: NotificationGutsManager,
@@ -52,28 +54,32 @@ class ViewConfigCoordinator @Inject internal constructor(
private var mDispatchUiModeChangeOnUserSwitched = false
private var mPipeline: NotifPipeline? = null
- private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
- override fun onUserSwitching(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
- log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
- mIsSwitchingUser = true
- }
+ private val mKeyguardUpdateCallback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onUserSwitching(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitching()")
+ log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+ mIsSwitchingUser = true
+ }
- override fun onUserSwitchComplete(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mKeyguardUpdateCallback.onUserSwitchComplete()")
- log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
- mIsSwitchingUser = false
- applyChangesOnUserSwitched()
+ override fun onUserSwitchComplete(userId: Int) {
+ colorUpdateLogger.logTriggerEvent(
+ "VCC.mKeyguardUpdateCallback.onUserSwitchComplete()"
+ )
+ log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+ mIsSwitchingUser = false
+ applyChangesOnUserSwitched()
+ }
}
- }
- private val mUserChangedListener = object : UserChangedListener {
- override fun onUserChanged(userId: Int) {
- colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
- log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
- applyChangesOnUserSwitched()
+ private val mUserChangedListener =
+ object : UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ colorUpdateLogger.logTriggerEvent("VCC.mUserChangedListener.onUserChanged()")
+ log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+ applyChangesOnUserSwitched()
+ }
}
- }
override fun attach(pipeline: NotifPipeline) {
mPipeline = pipeline
@@ -87,8 +93,8 @@ class ViewConfigCoordinator @Inject internal constructor(
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
MessagingMessage.dropCache()
MessagingGroup.dropCache()
@@ -104,8 +110,8 @@ class ViewConfigCoordinator @Inject internal constructor(
log {
val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
"ViewConfigCoordinator.onUiModeChanged()" +
- " isSwitchingUser=$mIsSwitchingUser" +
- " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
}
if (!mIsSwitchingUser) {
updateNotificationsOnUiModeChanged()
@@ -132,13 +138,13 @@ class ViewConfigCoordinator @Inject internal constructor(
}
private fun updateNotificationsOnUiModeChanged() {
- colorUpdateLogger.logEvent("VCC.updateNotificationsOnUiModeChanged()",
- "mode=" + mConfigurationController.nightModeName)
+ colorUpdateLogger.logEvent(
+ "VCC.updateNotificationsOnUiModeChanged()",
+ "mode=" + mConfigurationController.nightModeName,
+ )
log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
traceSection("updateNotifOnUiModeChanged") {
- mPipeline?.allNotifs?.forEach { entry ->
- entry.row?.onUiModeChanged()
- }
+ mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index cab4c1c88b2c..3c838e5b707e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.notification.collection.render
import android.content.Context
import android.view.View
+import com.android.app.tracing.traceSection
+import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -26,8 +28,6 @@ import com.android.systemui.statusbar.notification.collection.PipelineDumpable
import com.android.systemui.statusbar.notification.collection.PipelineDumper
import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
-import com.android.app.tracing.traceSection
-import com.android.systemui.shade.ShadeDisplayAware
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@@ -36,7 +36,9 @@ import dagger.assisted.AssistedInject
* Responsible for building and applying the "shade node spec": the list (tree) of things that
* currently populate the notification shade.
*/
-class ShadeViewManager @AssistedInject constructor(
+class ShadeViewManager
+@AssistedInject
+constructor(
@ShadeDisplayAware context: Context,
@Assisted listContainer: NotificationListContainer,
@Assisted private val stackController: NotifStackController,
@@ -45,13 +47,19 @@ class ShadeViewManager @AssistedInject constructor(
sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
nodeSpecBuilderLogger: NodeSpecBuilderLogger,
shadeViewDifferLogger: ShadeViewDifferLogger,
- private val viewBarn: NotifViewBarn
+ private val viewBarn: NotifViewBarn,
) : PipelineDumpable {
// We pass a shim view here because the listContainer may not actually have a view associated
// with it and the differ never actually cares about the root node's view.
private val rootController = RootNodeController(listContainer, View(context))
- private val specBuilder = NodeSpecBuilder(mediaContainerController, featureManager,
- sectionHeaderVisibilityProvider, viewBarn, nodeSpecBuilderLogger)
+ private val specBuilder =
+ NodeSpecBuilder(
+ mediaContainerController,
+ featureManager,
+ sectionHeaderVisibilityProvider,
+ viewBarn,
+ nodeSpecBuilderLogger,
+ )
private val viewDiffer = ShadeViewDiffer(rootController, shadeViewDifferLogger)
/** Method for attaching this manager to the pipeline. */
@@ -59,34 +67,36 @@ class ShadeViewManager @AssistedInject constructor(
renderStageManager.setViewRenderer(viewRenderer)
}
- override fun dumpPipeline(d: PipelineDumper) = with(d) {
- dump("rootController", rootController)
- dump("specBuilder", specBuilder)
- dump("viewDiffer", viewDiffer)
- }
+ override fun dumpPipeline(d: PipelineDumper) =
+ with(d) {
+ dump("rootController", rootController)
+ dump("specBuilder", specBuilder)
+ dump("viewDiffer", viewDiffer)
+ }
- private val viewRenderer = object : NotifViewRenderer {
+ private val viewRenderer =
+ object : NotifViewRenderer {
- override fun onRenderList(notifList: List<ListEntry>) {
- traceSection("ShadeViewManager.onRenderList") {
- viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+ override fun onRenderList(notifList: List<ListEntry>) {
+ traceSection("ShadeViewManager.onRenderList") {
+ viewDiffer.applySpec(specBuilder.buildNodeSpec(rootController, notifList))
+ }
}
- }
- override fun getStackController(): NotifStackController = stackController
+ override fun getStackController(): NotifStackController = stackController
- override fun getGroupController(group: GroupEntry): NotifGroupController =
- viewBarn.requireGroupController(group.requireSummary)
+ override fun getGroupController(group: GroupEntry): NotifGroupController =
+ viewBarn.requireGroupController(group.requireSummary)
- override fun getRowController(entry: NotificationEntry): NotifRowController =
- viewBarn.requireRowController(entry)
- }
+ override fun getRowController(entry: NotificationEntry): NotifRowController =
+ viewBarn.requireRowController(entry)
+ }
}
@AssistedFactory
interface ShadeViewManagerFactory {
fun create(
listContainer: NotificationListContainer,
- stackController: NotifStackController
+ stackController: NotifStackController,
): ShadeViewManager
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
index 925d4a588f09..4c2512988f4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/ReferenceNotificationsModule.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.dagger
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsModule
import com.android.systemui.statusbar.notification.row.NotificationRowModule
import dagger.Module
@@ -23,5 +24,12 @@ import dagger.Module
* A module that includes the standard notifications classes that most SysUI variants need. Variants
* are free to not include this module and instead write a custom notifications module.
*/
-@Module(includes = [NotificationsModule::class, NotificationRowModule::class])
+@Module(
+ includes =
+ [
+ NotificationsModule::class,
+ NotificationRowModule::class,
+ PromotedNotificationsModule::class,
+ ]
+)
object ReferenceNotificationsModule
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
index 7d374b051b76..dbe58337de11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/NotificationSettingsRepositoryModule.kt
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.data
+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
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.settings.SecureSettingsRepositoryModule
-import com.android.systemui.settings.SystemSettingsRepositoryModule
+import com.android.systemui.settings.UserSettingsRepositoryModule
import com.android.systemui.shared.notifications.data.repository.NotificationSettingsRepository
import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
@@ -32,9 +32,8 @@ import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import com.android.app.tracing.coroutines.launchTraced as launch
-@Module(includes = [SecureSettingsRepositoryModule::class, SystemSettingsRepositoryModule::class])
+@Module(includes = [UserSettingsRepositoryModule::class])
object NotificationSettingsRepositoryModule {
@Provides
@SysUISingleton
@@ -48,7 +47,8 @@ object NotificationSettingsRepositoryModule {
backgroundScope,
backgroundDispatcher,
secureSettingsRepository,
- systemSettingsRepository)
+ systemSettingsRepository,
+ )
@Provides
@IntoMap
@@ -57,7 +57,7 @@ object NotificationSettingsRepositoryModule {
fun provideCoreStartable(
@Application applicationScope: CoroutineScope,
repository: NotificationSettingsRepository,
- logger: VisualInterruptionDecisionLogger
+ logger: VisualInterruptionDecisionLogger,
) = CoreStartable {
applicationScope.launch {
repository.isCooldownEnabled.collect { value -> logger.logCooldownSetting(value) }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 697a6ce52ba9..cff5bef9fe69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -83,6 +83,9 @@ constructor(
// TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow
// instead of being separate.
topLevelRepresentativeNotifications
+ .map { notifs -> notifs.filter { it.isPromoted } }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
} else {
flowOf(emptyList())
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
index 10084517ec19..23da90d426c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractor.kt
@@ -33,6 +33,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationsProvider
import com.android.systemui.statusbar.notification.shared.ActiveNotificationEntryModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel
import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
@@ -50,6 +51,7 @@ class RenderNotificationListInteractor
constructor(
private val repository: ActiveNotificationListRepository,
private val sectionStyleProvider: SectionStyleProvider,
+ private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
/**
* Sets the current list of rendered notification entries as displayed in the notification list.
@@ -57,7 +59,11 @@ constructor(
fun setRenderedList(entries: List<ListEntry>) {
traceSection("RenderNotificationListInteractor.setRenderedList") {
repository.activeNotifications.update { existingModels ->
- buildActiveNotificationsStore(existingModels, sectionStyleProvider) {
+ buildActiveNotificationsStore(
+ existingModels,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ ) {
entries.forEach(::addListEntry)
setRankingsMap(entries)
}
@@ -69,13 +75,21 @@ constructor(
private fun buildActiveNotificationsStore(
existingModels: ActiveNotificationsStore,
sectionStyleProvider: SectionStyleProvider,
- block: ActiveNotificationsStoreBuilder.() -> Unit
+ promotedNotificationsProvider: PromotedNotificationsProvider,
+ block: ActiveNotificationsStoreBuilder.() -> Unit,
): ActiveNotificationsStore =
- ActiveNotificationsStoreBuilder(existingModels, sectionStyleProvider).apply(block).build()
+ ActiveNotificationsStoreBuilder(
+ existingModels,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ )
+ .apply(block)
+ .build()
private class ActiveNotificationsStoreBuilder(
private val existingModels: ActiveNotificationsStore,
private val sectionStyleProvider: SectionStyleProvider,
+ private val promotedNotificationsProvider: PromotedNotificationsProvider,
) {
private val builder = ActiveNotificationsStore.Builder()
@@ -96,7 +110,7 @@ private class ActiveNotificationsStoreBuilder(
existingModels.createOrReuse(
key = entry.key,
summary = summaryModel,
- children = childModels
+ children = childModels,
)
)
}
@@ -141,6 +155,7 @@ private class ActiveNotificationsStoreBuilder(
key = key,
groupKey = sbn.groupKey,
whenTime = sbn.notification.`when`,
+ isPromoted = promotedNotificationsProvider.shouldPromote(this),
isAmbient = sectionStyleProvider.isMinimized(this),
isRowDismissed = isRowDismissed,
isSilent = sectionStyleProvider.isSilent(this),
@@ -166,6 +181,7 @@ private fun ActiveNotificationsStore.createOrReuse(
key: String,
groupKey: String?,
whenTime: Long,
+ isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -189,6 +205,7 @@ private fun ActiveNotificationsStore.createOrReuse(
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -212,6 +229,7 @@ private fun ActiveNotificationsStore.createOrReuse(
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
@@ -236,6 +254,7 @@ private fun ActiveNotificationModel.isCurrent(
key: String,
groupKey: String?,
whenTime: Long,
+ isPromoted: Boolean,
isAmbient: Boolean,
isRowDismissed: Boolean,
isSilent: Boolean,
@@ -258,6 +277,7 @@ private fun ActiveNotificationModel.isCurrent(
key != this.key -> false
groupKey != this.groupKey -> false
whenTime != this.whenTime -> false
+ isPromoted != this.isPromoted -> false
isAmbient != this.isAmbient -> false
isRowDismissed != this.isRowDismissed -> false
isSilent != this.isSilent -> false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterView.java
index 96f47e5fdd52..a0515ca92cdd 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
@@ -459,8 +459,12 @@ public class FooterView extends StackScrollerDecorView {
Resources.Theme theme = mContext.getTheme();
final @ColorInt int onSurface = Utils.getColorAttrDefaultColor(mContext,
com.android.internal.R.attr.materialColorOnSurface);
+ // Same resource, separate drawables to prevent touch effects from showing on the wrong
+ // button.
final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
- final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable settingsBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+ final Drawable historyBg = NotifRedesignFooter.isEnabled()
+ ? theme.getDrawable(R.drawable.notif_footer_btn_background) : null;
final @ColorInt int scHigh;
if (!notificationFooterBackgroundTintOptimization()) {
scHigh = Utils.getColorAttrDefaultColor(mContext,
@@ -468,7 +472,10 @@ public class FooterView extends StackScrollerDecorView {
if (scHigh != 0) {
final ColorFilter bgColorFilter = new PorterDuffColorFilter(scHigh, SRC_ATOP);
clearAllBg.setColorFilter(bgColorFilter);
- manageBg.setColorFilter(bgColorFilter);
+ settingsBg.setColorFilter(bgColorFilter);
+ if (NotifRedesignFooter.isEnabled()) {
+ historyBg.setColorFilter(bgColorFilter);
+ }
}
} else {
scHigh = 0;
@@ -476,13 +483,13 @@ public class FooterView extends StackScrollerDecorView {
mClearAllButton.setBackground(clearAllBg);
mClearAllButton.setTextColor(onSurface);
if (NotifRedesignFooter.isEnabled()) {
- mSettingsButton.setBackground(manageBg);
+ mSettingsButton.setBackground(settingsBg);
mSettingsButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
- mHistoryButton.setBackground(manageBg);
+ mHistoryButton.setBackground(historyBg);
mHistoryButton.setCompoundDrawableTintList(ColorStateList.valueOf(onSurface));
} else {
- mManageOrHistoryButton.setBackground(manageBg);
+ mManageOrHistoryButton.setBackground(settingsBg);
mManageOrHistoryButton.setTextColor(onSurface);
}
mSeenNotifsFooterTextView.setTextColor(onSurface);
@@ -492,7 +499,7 @@ public class FooterView extends StackScrollerDecorView {
colorUpdateLogger.logEvent("Footer.updateColors()",
"textColor(onSurface)=" + hexColorString(onSurface)
+ " backgroundTint(surfaceContainerHigh)=" + hexColorString(scHigh)
- + " background=" + DrawableDumpKt.dumpToString(manageBg));
+ + " background=" + DrawableDumpKt.dumpToString(settingsBg));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 71cddc99b564..52336be742cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -75,7 +75,7 @@ constructor(
private val bubbles: Optional<Bubbles>,
@ShadeDisplayAware private val context: Context,
private val notificationManager: NotificationManager,
- private val settingsInteractor: NotificationSettingsInteractor
+ private val settingsInteractor: NotificationSettingsInteractor,
) : VisualInterruptionDecisionProvider {
init {
@@ -89,7 +89,7 @@ constructor(
private class DecisionImpl(
override val shouldInterrupt: Boolean,
- override val logReason: String
+ override val logReason: String,
) : Decision
private data class LoggableDecision
@@ -107,7 +107,7 @@ constructor(
LoggableDecision(
DecisionImpl(
shouldInterrupt = false,
- logReason = "${legacySuppressor.name}.$methodName"
+ logReason = "${legacySuppressor.name}.$methodName",
)
)
@@ -123,7 +123,7 @@ constructor(
private class FullScreenIntentDecisionImpl(
val entry: NotificationEntry,
- private val fsiDecision: FullScreenIntentDecisionProvider.Decision
+ private val fsiDecision: FullScreenIntentDecisionProvider.Decision,
) : FullScreenIntentDecision, Loggable {
var hasBeenLogged = false
@@ -154,7 +154,7 @@ constructor(
deviceProvisionedController,
keyguardStateController,
powerManager,
- statusBarStateController
+ statusBarStateController,
)
private val legacySuppressors = mutableSetOf<NotificationInterruptSuppressor>()
@@ -197,7 +197,7 @@ constructor(
context,
notificationManager,
logger,
- systemSettings
+ systemSettings,
)
)
avalancheProvider.register()
@@ -290,7 +290,7 @@ constructor(
private fun logDecision(
type: VisualInterruptionType,
entry: NotificationEntry,
- loggableDecision: LoggableDecision
+ loggableDecision: LoggableDecision,
) {
if (!loggableDecision.isSpammy || logger.spew) {
logger.logDecision(type.name, entry, loggableDecision.decision)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
new file mode 100644
index 000000000000..632421980772
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUi.kt
@@ -0,0 +1,61 @@
+/*
+ * 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.statusbar.notification.promoted
+
+import android.app.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the promoted ongoing notifications UI flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object PromotedNotificationUi {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the refactor enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.uiRichOngoing()
+
+ /**
+ * 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 not enabled to ensure that the refactor author catches issues in testing.
+ * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
+ */
+ @JvmStatic
+ inline fun assertInNewMode() = RefactorFlagUtils.assertInNewMode(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/settings/SecureSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
index 6199a83f9075..4be12bd44a29 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/SecureSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsModule.kt
@@ -14,25 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.settings
+package com.android.systemui.statusbar.notification.promoted
-import android.content.ContentResolver
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SecureSettingsRepositoryImpl
+import dagger.Binds
import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
@Module
-object SecureSettingsRepositoryModule {
- @JvmStatic
- @Provides
+abstract class PromotedNotificationsModule {
+ @Binds
@SysUISingleton
- fun provideSecureSettingsRepository(
- contentResolver: ContentResolver,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ): SecureSettingsRepository =
- SecureSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
+ abstract fun bindPromotedNotificationsProvider(
+ impl: PromotedNotificationsProviderImpl
+ ): PromotedNotificationsProvider
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
new file mode 100644
index 000000000000..691dc6f5ccac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProvider.kt
@@ -0,0 +1,38 @@
+/*
+ * 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.statusbar.notification.promoted
+
+import android.app.Notification.FLAG_PROMOTED_ONGOING
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+/** A provider for making decisions on which notifications should be promoted. */
+interface PromotedNotificationsProvider {
+ /** Returns true if the given notification should be promoted and false otherwise. */
+ fun shouldPromote(entry: NotificationEntry): Boolean
+}
+
+@SysUISingleton
+open class PromotedNotificationsProviderImpl @Inject constructor() : PromotedNotificationsProvider {
+ override fun shouldPromote(entry: NotificationEntry): Boolean {
+ if (!PromotedNotificationUi.isEnabled) {
+ return false
+ }
+ return (entry.sbn.notification.flags and FLAG_PROMOTED_ONGOING) != 0
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
index e233deffe42f..916414548fbd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BigPictureIconManager.kt
@@ -25,6 +25,7 @@ import android.util.Dumpable
import android.util.Log
import android.util.Size
import androidx.annotation.MainThread
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.R
import com.android.internal.widget.NotificationDrawableConsumer
import com.android.internal.widget.NotificationIconManager
@@ -45,7 +46,6 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
private const val TAG = "BigPicImageLoader"
@@ -67,7 +67,7 @@ constructor(
private val statsManager: BigPictureStatsManager,
@Application private val scope: CoroutineScope,
@Main private val mainDispatcher: CoroutineDispatcher,
- @Background private val bgDispatcher: CoroutineDispatcher
+ @Background private val bgDispatcher: CoroutineDispatcher,
) : NotificationIconManager, Dumpable {
private var lastLoadingJob: Job? = null
@@ -153,7 +153,7 @@ constructor(
private fun checkPlaceHolderSizeForDrawable(
displayedState: DrawableState,
- newDrawable: Drawable
+ newDrawable: Drawable,
) {
if (displayedState is PlaceHolder) {
val (oldWidth, oldHeight) = displayedState.drawableSize
@@ -163,7 +163,7 @@ constructor(
Log.e(
TAG,
"Mismatch in dimensions, when replacing PlaceHolder " +
- "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight."
+ "$oldWidth X $oldHeight with Drawable $newWidth X $newHeight.",
)
}
}
@@ -184,9 +184,8 @@ constructor(
displayedState = drawableAndState?.second ?: Empty
}
- private fun startLoadingJob(icon: Icon): Job = scope.launch {
- statsManager.measure { loadImage(icon) }
- }
+ private fun startLoadingJob(icon: Icon): Job =
+ scope.launch { statsManager.measure { loadImage(icon) } }
private suspend fun loadImage(icon: Icon) {
val drawableAndState = withContext(bgDispatcher) { loadImageSync(icon) }
@@ -254,9 +253,12 @@ constructor(
private sealed class DrawableState(open val icon: Icon?) {
data object Initial : DrawableState(null)
+
data object Empty : DrawableState(null)
+
data class PlaceHolder(override val icon: Icon, val drawableSize: Size) :
DrawableState(icon)
+
data class FullImage(override val icon: Icon, val drawableSize: Size) : DrawableState(icon)
}
}
@@ -298,7 +300,7 @@ private fun Size.resizeToMax(maxWidth: Int, maxHeight: Int): Size {
}
private val Drawable.intrinsicSize
- get() = Size(/*width=*/ intrinsicWidth, /*height=*/ intrinsicHeight)
+ get() = Size(/* width= */ intrinsicWidth, /* height= */ intrinsicHeight)
private operator fun Size.component1() = width
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
index b166defb96a2..2dcb706234b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImpl.kt
@@ -95,7 +95,7 @@ constructor(
private val smartReplyStateInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
private val headsUpStyleProvider: HeadsUpStyleProvider,
- private val logger: NotificationRowContentBinderLogger
+ private val logger: NotificationRowContentBinderLogger,
) : NotificationRowContentBinder {
init {
@@ -110,7 +110,7 @@ constructor(
@InflationFlag contentToBind: Int,
bindParams: BindParams,
forceInflate: Boolean,
- callback: InflationCallback?
+ callback: InflationCallback?,
) {
if (row.isRemoved) {
// We don't want to reinflate anything for removed notifications. Otherwise views might
@@ -147,7 +147,7 @@ constructor(
/* isMediaFlagEnabled = */ smartReplyStateInflater,
notifLayoutInflaterFactoryProvider,
headsUpStyleProvider,
- logger
+ logger,
)
if (inflateSynchronously) {
task.onPostExecute(task.doInBackground())
@@ -165,7 +165,7 @@ constructor(
@InflationFlag reInflateFlags: Int,
builder: Notification.Builder,
packageContext: Context,
- smartRepliesInflater: SmartReplyStateInflater
+ smartRepliesInflater: SmartReplyStateInflater,
): InflationProgress {
val systemUIContext = row.context
val result =
@@ -229,7 +229,7 @@ constructor(
row,
remoteInputManager.remoteViewsOnClickHandler,
/* callback= */ null,
- logger
+ logger,
)
return result
}
@@ -246,7 +246,7 @@ constructor(
override fun unbindContent(
entry: NotificationEntry,
row: ExpandableNotificationRow,
- @InflationFlag contentToUnbind: Int
+ @InflationFlag contentToUnbind: Int,
) {
logger.logUnbinding(entry, contentToUnbind)
var curFlag = 1
@@ -268,7 +268,7 @@ constructor(
private fun freeNotificationView(
entry: NotificationEntry,
row: ExpandableNotificationRow,
- @InflationFlag inflateFlag: Int
+ @InflationFlag inflateFlag: Int,
) {
when (inflateFlag) {
FLAG_CONTENT_VIEW_CONTRACTED ->
@@ -319,7 +319,7 @@ constructor(
*/
private fun cancelContentViewFrees(
row: ExpandableNotificationRow,
- @InflationFlag contentViews: Int
+ @InflationFlag contentViews: Int,
) {
if (contentViews and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
row.privateLayout.removeContentInactiveRunnable(VISIBLE_TYPE_CONTRACTED)
@@ -372,7 +372,7 @@ constructor(
private val smartRepliesInflater: SmartReplyStateInflater,
private val notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
private val headsUpStyleProvider: HeadsUpStyleProvider,
- private val logger: NotificationRowContentBinderLogger
+ private val logger: NotificationRowContentBinderLogger,
) : AsyncTask<Void, Void, Result<InflationProgress>>(), InflationCallback, InflationTask {
private val context: Context
get() = row.context
@@ -393,7 +393,7 @@ constructor(
context.packageManager.getApplicationInfoAsUser(
packageName,
PackageManager.MATCH_UNINSTALLED_PACKAGES,
- userId
+ userId,
)
} catch (e: PackageManager.NameNotFoundException) {
return
@@ -442,11 +442,11 @@ constructor(
notifLayoutInflaterFactoryProvider = notifLayoutInflaterFactoryProvider,
headsUpStyleProvider = headsUpStyleProvider,
conversationProcessor = conversationProcessor,
- logger = logger
+ logger = logger,
)
logger.logAsyncTaskProgress(
entry,
- "getting existing smart reply state (on wrong thread!)"
+ "getting existing smart reply state (on wrong thread!)",
)
val previousSmartReplyState: InflatedSmartReplyState? = row.existingSmartReplyState
logger.logAsyncTaskProgress(entry, "inflating smart reply views")
@@ -469,7 +469,7 @@ constructor(
reInflateFlags,
entry,
context,
- logger
+ logger,
)
}
}
@@ -483,7 +483,7 @@ constructor(
reInflateFlags,
entry,
context,
- logger
+ logger,
)
}
}
@@ -513,7 +513,7 @@ constructor(
row,
remoteViewClickHandler,
this /* callback */,
- logger
+ logger,
)
}
.onFailure { error -> handleError(error as Exception) }
@@ -530,7 +530,7 @@ constructor(
Log.e(TAG, "couldn't inflate view for notification $ident", e)
callback?.handleInflationException(
row.entry,
- InflationException("Couldn't inflate contentViews$e")
+ InflationException("Couldn't inflate contentViews$e"),
)
// Cancel any image loading tasks, not useful any more
@@ -618,7 +618,7 @@ constructor(
packageContext: Context,
previousSmartReplyState: InflatedSmartReplyState?,
inflater: SmartReplyStateInflater,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
) {
val inflateContracted =
(reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0 &&
@@ -641,7 +641,7 @@ constructor(
packageContext,
entry,
previousSmartReplyState,
- result.inflatedSmartReplyState!!
+ result.inflatedSmartReplyState!!,
)
}
if (inflateHeadsUp) {
@@ -652,7 +652,7 @@ constructor(
packageContext,
entry,
previousSmartReplyState,
- result.inflatedSmartReplyState!!
+ result.inflatedSmartReplyState!!,
)
}
}
@@ -670,7 +670,7 @@ constructor(
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
conversationProcessor: ConversationNotificationProcessor,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): InflationProgress {
// process conversations and extract the messaging style
val messagingStyle =
@@ -713,7 +713,7 @@ constructor(
logger.logAsyncTaskProgress(entry, "inflating public single line view model")
SingleLineViewInflater.inflateRedactedSingleLineViewModel(
systemUIContext,
- entry.ranking.isConversation
+ entry.ranking.isConversation,
)
} else null
@@ -746,7 +746,7 @@ constructor(
row: ExpandableNotificationRow,
notifLayoutInflaterFactoryProvider: NotifLayoutInflaterFactory.Provider,
headsUpStyleProvider: HeadsUpStyleProvider,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): NewRemoteViews {
return TraceUtils.trace("NotificationContentInflater.createRemoteViews") {
val entryForLogging: NotificationEntry = row.entry
@@ -754,7 +754,7 @@ constructor(
if (reInflateFlags and FLAG_CONTENT_VIEW_CONTRACTED != 0) {
logger.logAsyncTaskProgress(
entryForLogging,
- "creating contracted remote view"
+ "creating contracted remote view",
)
createContentView(builder, isMinimized, usesIncreasedHeight)
} else null
@@ -762,7 +762,7 @@ constructor(
if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
logger.logAsyncTaskProgress(
entryForLogging,
- "creating expanded remote view"
+ "creating expanded remote view",
)
createExpandedView(builder, isMinimized)
} else null
@@ -770,7 +770,7 @@ constructor(
if (reInflateFlags and FLAG_CONTENT_VIEW_HEADS_UP != 0) {
logger.logAsyncTaskProgress(
entryForLogging,
- "creating heads up remote view"
+ "creating heads up remote view",
)
val isHeadsUpCompact = headsUpStyleProvider.shouldApplyCompactStyle()
if (isHeadsUpCompact) {
@@ -791,7 +791,7 @@ constructor(
) {
logger.logAsyncTaskProgress(
entryForLogging,
- "creating group summary remote view"
+ "creating group summary remote view",
)
builder.makeNotificationGroupHeader()
} else null
@@ -802,7 +802,7 @@ constructor(
) {
logger.logAsyncTaskProgress(
entryForLogging,
- "creating low-priority group summary remote view"
+ "creating low-priority group summary remote view",
)
builder.makeLowPriorityContentView(true /* useRegularSubtext */)
} else null
@@ -812,7 +812,7 @@ constructor(
expanded = expanded,
public = public,
normalGroupHeader = normalGroupHeader,
- minimizedGroupHeader = minimizedGroupHeader
+ minimizedGroupHeader = minimizedGroupHeader,
)
.withLayoutInflaterFactory(row, notifLayoutInflaterFactoryProvider)
}
@@ -820,7 +820,7 @@ constructor(
private fun NewRemoteViews.withLayoutInflaterFactory(
row: ExpandableNotificationRow,
- provider: NotifLayoutInflaterFactory.Provider
+ provider: NotifLayoutInflaterFactory.Provider,
): NewRemoteViews {
contracted?.let {
it.layoutInflaterFactory = provider.provide(row, FLAG_CONTENT_VIEW_CONTRACTED)
@@ -848,7 +848,7 @@ constructor(
row: ExpandableNotificationRow,
remoteViewClickHandler: InteractionHandler?,
callback: InflationCallback?,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): CancellationSignal {
Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row))
val privateLayout = row.privateLayout
@@ -859,7 +859,7 @@ constructor(
val isNewView =
!canReapplyRemoteView(
newView = result.remoteViews.contracted,
- oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
@@ -890,7 +890,7 @@ constructor(
existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
flag = FLAG_CONTENT_VIEW_EXPANDED
@@ -898,7 +898,7 @@ constructor(
val isNewView =
!canReapplyRemoteView(
newView = result.remoteViews.expanded,
- oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
@@ -929,7 +929,7 @@ constructor(
existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_EXPANDED),
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
flag = FLAG_CONTENT_VIEW_HEADS_UP
@@ -937,7 +937,7 @@ constructor(
val isNewView =
!canReapplyRemoteView(
newView = result.remoteViews.headsUp,
- oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
@@ -968,7 +968,7 @@ constructor(
existingWrapper = privateLayout.getVisibleWrapper(VISIBLE_TYPE_HEADSUP),
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
flag = FLAG_CONTENT_VIEW_PUBLIC
@@ -976,7 +976,7 @@ constructor(
val isNewView =
!canReapplyRemoteView(
newView = result.remoteViews.public,
- oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)
+ oldView = remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
@@ -1007,7 +1007,7 @@ constructor(
existingWrapper = publicLayout.getVisibleWrapper(VISIBLE_TYPE_CONTRACTED),
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
if (AsyncGroupHeaderViewInflation.isEnabled) {
@@ -1018,7 +1018,7 @@ constructor(
!canReapplyRemoteView(
newView = result.remoteViews.normalGroupHeader,
oldView =
- remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER)
+ remoteViewCache.getCachedView(entry, FLAG_GROUP_SUMMARY_HEADER),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
@@ -1049,7 +1049,7 @@ constructor(
existingWrapper = childrenContainer.notificationHeaderWrapper,
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
if (reInflateFlags and FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER != 0) {
@@ -1059,15 +1059,15 @@ constructor(
oldView =
remoteViewCache.getCachedView(
entry,
- FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER
- )
+ FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER,
+ ),
)
val applyCallback: ApplyCallback =
object : ApplyCallback() {
override fun setResultView(v: View) {
logger.logAsyncTaskProgress(
entry,
- "low-priority group header view applied"
+ "low-priority group header view applied",
)
result.inflatedMinimizedGroupHeaderView =
v as NotificationHeaderView?
@@ -1095,7 +1095,7 @@ constructor(
existingWrapper = childrenContainer.minimizedGroupHeaderWrapper,
runningInflations = runningInflations,
applyCallback = applyCallback,
- logger = logger
+ logger = logger,
)
}
}
@@ -1110,7 +1110,7 @@ constructor(
callback,
entry,
row,
- logger
+ logger,
)
val cancellationSignal = CancellationSignal()
cancellationSignal.setOnCancelListener {
@@ -1142,7 +1142,7 @@ constructor(
existingWrapper: NotificationViewWrapper?,
runningInflations: HashMap<Int, CancellationSignal>,
applyCallback: ApplyCallback,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
) {
val newContentView: RemoteViews = applyCallback.remoteView
if (inflateSynchronously) {
@@ -1152,7 +1152,7 @@ constructor(
newContentView.apply(
result.packageContext,
parentLayout,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
validateView(v, entry, row.resources)
applyCallback.setResultView(v)
@@ -1162,7 +1162,7 @@ constructor(
newContentView.reapply(
result.packageContext,
existingView,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
validateView(existingView, entry, row.resources)
existingWrapper.onReinflated()
@@ -1174,7 +1174,7 @@ constructor(
row.entry,
callback,
logger,
- "applying view synchronously"
+ "applying view synchronously",
)
// Add a running inflation to make sure we don't trigger callbacks.
// Safe to do because only happens in tests.
@@ -1199,7 +1199,7 @@ constructor(
row.entry,
callback,
logger,
- "applied invalid view"
+ "applied invalid view",
)
runningInflations.remove(inflationId)
return
@@ -1219,7 +1219,7 @@ constructor(
callback,
entry,
row,
- logger
+ logger,
)
}
@@ -1234,20 +1234,20 @@ constructor(
newContentView.apply(
result.packageContext,
parentLayout,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
} else {
newContentView.reapply(
result.packageContext,
existingView,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
existingView!!
}
Log.wtf(
TAG,
"Async Inflation failed but normal inflation finished normally.",
- e
+ e,
)
onViewApplied(newView)
} catch (anotherException: Exception) {
@@ -1258,7 +1258,7 @@ constructor(
row.entry,
callback,
logger,
- "applying view"
+ "applying view",
)
}
}
@@ -1270,7 +1270,7 @@ constructor(
parentLayout,
inflationExecutor,
listener,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
} else {
newContentView.reapplyAsync(
@@ -1278,7 +1278,7 @@ constructor(
existingView,
inflationExecutor,
listener,
- remoteViewClickHandler
+ remoteViewClickHandler,
)
}
runningInflations[inflationId] = cancellationSignal
@@ -1299,7 +1299,7 @@ constructor(
private fun satisfiesMinHeightRequirement(
view: View,
entry: NotificationEntry,
- resources: Resources
+ resources: Resources,
): Boolean {
return if (!requiresHeightCheck(entry)) {
true
@@ -1353,7 +1353,7 @@ constructor(
notification: NotificationEntry,
callback: InflationCallback?,
logger: NotificationRowContentBinderLogger,
- logContext: String
+ logContext: String,
) {
Assert.isMainThread()
logger.logAsyncTaskException(notification, logContext, e)
@@ -1375,7 +1375,7 @@ constructor(
endListener: InflationCallback?,
entry: NotificationEntry,
row: ExpandableNotificationRow,
- logger: NotificationRowContentBinderLogger
+ logger: NotificationRowContentBinderLogger,
): Boolean {
Assert.isMainThread()
if (runningInflations.isNotEmpty()) {
@@ -1439,19 +1439,19 @@ constructor(
FLAG_CONTENT_VIEW_CONTRACTED,
result.remoteViews.contracted,
result.inflatedContentView,
- privateLayout::setContractedChild
+ privateLayout::setContractedChild,
)
remoteViewsUpdater.setContentView(
FLAG_CONTENT_VIEW_EXPANDED,
result.remoteViews.expanded,
result.inflatedExpandedView,
- privateLayout::setExpandedChild
+ privateLayout::setExpandedChild,
)
remoteViewsUpdater.setSmartReplies(
FLAG_CONTENT_VIEW_EXPANDED,
result.remoteViews.expanded,
result.expandedInflatedSmartReplies,
- privateLayout::setExpandedInflatedSmartReplies
+ privateLayout::setExpandedInflatedSmartReplies,
)
if (reInflateFlags and FLAG_CONTENT_VIEW_EXPANDED != 0) {
row.setExpandable(result.remoteViews.expanded != null)
@@ -1460,19 +1460,19 @@ constructor(
FLAG_CONTENT_VIEW_HEADS_UP,
result.remoteViews.headsUp,
result.inflatedHeadsUpView,
- privateLayout::setHeadsUpChild
+ privateLayout::setHeadsUpChild,
)
remoteViewsUpdater.setSmartReplies(
FLAG_CONTENT_VIEW_HEADS_UP,
result.remoteViews.headsUp,
result.headsUpInflatedSmartReplies,
- privateLayout::setHeadsUpInflatedSmartReplies
+ privateLayout::setHeadsUpInflatedSmartReplies,
)
remoteViewsUpdater.setContentView(
FLAG_CONTENT_VIEW_PUBLIC,
result.remoteViews.public,
result.inflatedPublicView,
- publicLayout::setContractedChild
+ publicLayout::setContractedChild,
)
if (AsyncGroupHeaderViewInflation.isEnabled) {
remoteViewsUpdater.setContentView(
@@ -1540,7 +1540,7 @@ constructor(
private fun createExpandedView(
builder: Notification.Builder,
- isMinimized: Boolean
+ isMinimized: Boolean,
): RemoteViews? {
@Suppress("DEPRECATION")
val bigContentView: RemoteViews? = builder.createBigContentView()
@@ -1558,7 +1558,7 @@ constructor(
private fun createContentView(
builder: Notification.Builder,
isMinimized: Boolean,
- useLarge: Boolean
+ useLarge: Boolean,
): RemoteViews {
return if (isMinimized) {
builder.makeLowPriorityContentView(false /* useRegularSubtext */)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
index cf19938aa533..19a92a2230ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/ActiveNotificationModel.kt
@@ -36,6 +36,8 @@ data class ActiveNotificationModel(
val groupKey: String?,
/** When this notification was posted. */
val whenTime: Long,
+ /** True if this notification should be promoted and false otherwise. */
+ val isPromoted: Boolean,
/** Is this entry in the ambient / minimized section (lowest priority)? */
val isAmbient: Boolean,
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index c9a0010c0de7..31e4d2cac50c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -26,7 +26,14 @@ import com.android.systemui.statusbar.notification.SourceType
import com.android.systemui.statusbar.notification.collection.NotificationClassificationFlag
import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
-import com.android.systemui.statusbar.notification.dagger.*
+import com.android.systemui.statusbar.notification.dagger.AlertingHeader
+import com.android.systemui.statusbar.notification.dagger.IncomingHeader
+import com.android.systemui.statusbar.notification.dagger.NewsHeader
+import com.android.systemui.statusbar.notification.dagger.PeopleHeader
+import com.android.systemui.statusbar.notification.dagger.PromoHeader
+import com.android.systemui.statusbar.notification.dagger.RecsHeader
+import com.android.systemui.statusbar.notification.dagger.SilentHeader
+import com.android.systemui.statusbar.notification.dagger.SocialHeader
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.SectionProvider
@@ -54,7 +61,7 @@ internal constructor(
@NewsHeader private val newsHeaderController: SectionHeaderController,
@SocialHeader private val socialHeaderController: SectionHeaderController,
@RecsHeader private val recsHeaderController: SectionHeaderController,
- @PromoHeader private val promoHeaderController: SectionHeaderController
+ @PromoHeader private val promoHeaderController: SectionHeaderController,
) : SectionProvider {
private val configurationListener =
@@ -136,14 +143,16 @@ internal constructor(
override fun beginsSection(view: View, previous: View?): Boolean =
view === silentHeaderView ||
- view === mediaControlsView ||
- view === peopleHeaderView ||
- view === alertingHeaderView ||
- view === incomingHeaderView ||
- (NotificationClassificationFlag.isEnabled && (view === newsHeaderView
- || view === socialHeaderView || view === recsHeaderView
- || view === promoHeaderView)) ||
- getBucket(view) != getBucket(previous)
+ view === mediaControlsView ||
+ view === peopleHeaderView ||
+ view === alertingHeaderView ||
+ view === incomingHeaderView ||
+ (NotificationClassificationFlag.isEnabled &&
+ (view === newsHeaderView ||
+ view === socialHeaderView ||
+ view === recsHeaderView ||
+ view === promoHeaderView)) ||
+ getBucket(view) != getBucket(previous)
private fun getBucket(view: View?): Int? =
when {
@@ -165,6 +174,7 @@ internal constructor(
data class Many(val first: ExpandableView, val last: ExpandableView) : SectionBounds()
data class One(val lone: ExpandableView) : SectionBounds()
+
object None : SectionBounds()
fun addNotif(notif: ExpandableView): SectionBounds =
@@ -183,7 +193,7 @@ internal constructor(
private fun NotificationSection.setFirstAndLastVisibleChildren(
first: ExpandableView?,
- last: ExpandableView?
+ last: ExpandableView?,
): Boolean {
val firstChanged = setFirstVisibleChild(first)
val lastChanged = setLastVisibleChild(last)
@@ -198,7 +208,7 @@ internal constructor(
*/
fun updateFirstAndLastViewsForAllSections(
sections: Array<NotificationSection>,
- children: List<ExpandableView>
+ children: List<ExpandableView>,
): Boolean {
// Create mapping of bucket to section
val sectionBounds =
@@ -213,7 +223,7 @@ internal constructor(
.foldToSparseArray(
SectionBounds.None,
size = sections.size,
- operation = SectionBounds::addNotif
+ operation = SectionBounds::addNotif,
)
// Build a set of the old first/last Views of the sections
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 99efba43b12f..7389086296a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -171,12 +171,14 @@ import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shade.QuickSettingsController;
import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shade.ShadeExpansionListener;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
@@ -295,7 +297,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
};
void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
- StatusBarSimpleFragment.assertInLegacyMode();
+ StatusBarConnectedDisplays.assertInLegacyMode();
mStatusBarWindowState = state;
updateBubblesVisibility();
}
@@ -366,6 +368,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private PhoneStatusBarViewController mPhoneStatusBarViewController;
private PhoneStatusBarTransitions mStatusBarTransitions;
+ private final Provider<StatusBarLongPressGestureDetector> mStatusBarLongPressGestureDetector;
private final AuthRippleController mAuthRippleController;
@WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
private final NotificationShadeWindowController mNotificationShadeWindowController;
@@ -671,6 +674,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
ShadeController shadeController,
WindowRootViewVisibilityInteractor windowRootViewVisibilityInteractor,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ Provider<StatusBarLongPressGestureDetector> statusBarLongPressGestureDetector,
ViewMediatorCallback viewMediatorCallback,
InitController initController,
@Named(TIME_TICK_HANDLER_NAME) Handler timeTickHandler,
@@ -778,6 +782,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mShadeController = shadeController;
mWindowRootViewVisibilityInteractor = windowRootViewVisibilityInteractor;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
mKeyguardViewMediatorCallback = viewMediatorCallback;
mInitController = initController;
mPluginDependencyProvider = pluginDependencyProvider;
@@ -1527,6 +1532,11 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
// to touch outside the customizer to close it, such as on the status or nav bar.
mShadeController.onStatusBarTouch(event);
}
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mStatusBarLongPressGestureDetector.get().handleTouch(event);
+ }
+
return getNotificationShadeWindowView().onTouchEvent(event);
};
}
@@ -1589,8 +1599,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
mRemoteInputManager.addControllerCallback(mStatusBarKeyguardViewManager);
-
- mLightBarController.setBiometricUnlockController(mBiometricUnlockController);
Trace.endSection();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index be2fb68ab88d..2433b78fc183 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -50,6 +50,8 @@ import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.keyguard.ui.viewmodel.GlanceableHubToLockscreenTransitionViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTransitionViewModel;
import com.android.systemui.log.core.LogLevel;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
@@ -82,6 +84,8 @@ import com.android.systemui.util.settings.SecureSettings;
import kotlin.Unit;
+import kotlinx.coroutines.CoroutineDispatcher;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -108,6 +112,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
R.id.keyguard_hun_animator_end_tag,
R.id.keyguard_hun_animator_start_tag);
+ private final CoroutineDispatcher mCoroutineDispatcher;
private final CarrierTextController mCarrierTextController;
private final ConfigurationController mConfigurationController;
private final SystemStatusAnimationScheduler mAnimationScheduler;
@@ -133,6 +138,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private final Object mLock = new Object();
private final KeyguardLogger mLogger;
private final CommunalSceneInteractor mCommunalSceneInteractor;
+ private final GlanceableHubToLockscreenTransitionViewModel mHubToLockscreenTransitionViewModel;
+ private final LockscreenToGlanceableHubTransitionViewModel mLockscreenToHubTransitionViewModel;
private View mSystemIconsContainer;
private final StatusOverlayHoverListenerFactory mStatusOverlayHoverListenerFactory;
@@ -249,9 +256,20 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private boolean mCommunalShowing;
private final Consumer<Boolean> mCommunalConsumer = (communalShowing) -> {
+ updateCommunalShowing(communalShowing);
+ };
+
+ @VisibleForTesting
+ void updateCommunalShowing(boolean communalShowing) {
mCommunalShowing = communalShowing;
+
+ // When communal is hidden (either by transition or state change), set alpha to fully
+ // visible.
+ if (!mCommunalShowing) {
+ setAlpha(-1f);
+ }
updateViewState();
- };
+ }
private final DisableStateTracker mDisableStateTracker;
@@ -277,6 +295,15 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
private boolean mShowingKeyguardHeadsUp;
private StatusBarSystemEventDefaultAnimator mSystemEventAnimator;
private float mSystemEventAnimatorAlpha = 1;
+ private final Consumer<Float> mToGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+ updateCommunalAlphaTransition(alpha);
+
+ private final Consumer<Float> mFromGlanceableHubStatusBarAlphaConsumer = (alpha) ->
+ updateCommunalAlphaTransition(alpha);
+
+ @VisibleForTesting void updateCommunalAlphaTransition(float alpha) {
+ setAlpha(!mCommunalShowing || alpha == 0 ? -1 : alpha);
+ }
/**
* The alpha value to be set on the View. If -1, this value is to be ignored.
@@ -285,6 +312,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
@Inject
public KeyguardStatusBarViewController(
+ @Main CoroutineDispatcher dispatcher,
KeyguardStatusBarView view,
CarrierTextController carrierTextController,
ConfigurationController configurationController,
@@ -310,9 +338,14 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
@Background Executor backgroundExecutor,
KeyguardLogger logger,
StatusOverlayHoverListenerFactory statusOverlayHoverListenerFactory,
- CommunalSceneInteractor communalSceneInteractor
+ CommunalSceneInteractor communalSceneInteractor,
+ GlanceableHubToLockscreenTransitionViewModel
+ glanceableHubToLockscreenTransitionViewModel,
+ LockscreenToGlanceableHubTransitionViewModel
+ lockscreenToGlanceableHubTransitionViewModel
) {
super(view);
+ mCoroutineDispatcher = dispatcher;
mCarrierTextController = carrierTextController;
mConfigurationController = configurationController;
mAnimationScheduler = animationScheduler;
@@ -337,6 +370,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mBackgroundExecutor = backgroundExecutor;
mLogger = logger;
mCommunalSceneInteractor = communalSceneInteractor;
+ mHubToLockscreenTransitionViewModel = glanceableHubToLockscreenTransitionViewModel;
+ mLockscreenToHubTransitionViewModel = lockscreenToGlanceableHubTransitionViewModel;
mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
mKeyguardStateController.addCallback(
@@ -418,7 +453,12 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
UserHandle.USER_ALL);
updateUserSwitcher();
onThemeChanged();
- collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer);
+ collectFlow(mView, mCommunalSceneInteractor.isCommunalVisible(), mCommunalConsumer,
+ mCoroutineDispatcher);
+ collectFlow(mView, mLockscreenToHubTransitionViewModel.getStatusBarAlpha(),
+ mToGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
+ collectFlow(mView, mHubToLockscreenTransitionViewModel.getStatusBarAlpha(),
+ mFromGlanceableHubStatusBarAlphaConsumer, mCoroutineDispatcher);
}
@Override
@@ -573,7 +613,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
&& !mDozing
&& !hideForBypass
&& !mDisableStateTracker.isDisabled()
- && !mCommunalShowing
+ && (!mCommunalShowing || mExplicitAlpha != -1)
? View.VISIBLE : View.INVISIBLE;
updateViewState(newAlpha, newVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
new file mode 100644
index 000000000000..a6374a66806b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.statusbar.phone
+
+import android.view.WindowInsetsController
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.CoreStartable
+
+/** Controls how light status bar flag applies to the icons. */
+interface LightBarController : CoreStartable {
+
+ fun stop()
+
+ fun setNavigationBar(navigationBar: LightBarTransitionsController)
+
+ fun onNavigationBarAppearanceChanged(
+ @WindowInsetsController.Appearance appearance: Int,
+ nbModeChanged: Boolean,
+ navigationBarMode: Int,
+ navbarColorManagedByIme: Boolean,
+ )
+
+ fun onNavigationBarModeChanged(newBarMode: Int)
+
+ fun setQsCustomizing(customizing: Boolean)
+
+ /** Set if Quick Settings is fully expanded, which affects notification scrim visibility. */
+ fun setQsExpanded(expanded: Boolean)
+
+ /** Set if Global Actions dialog is visible, which requires dark mode (light buttons). */
+ fun setGlobalActionsVisible(visible: Boolean)
+
+ /**
+ * Controls the light status bar temporarily for back navigation.
+ *
+ * @param appearance the customized appearance.
+ */
+ fun customizeStatusBarAppearance(appearance: AppearanceRegion)
+
+ /**
+ * Sets whether the direct-reply is in use or not.
+ *
+ * @param directReplying `true` when the direct-reply is in-use.
+ */
+ fun setDirectReplying(directReplying: Boolean)
+
+ fun setScrimState(
+ scrimState: ScrimState,
+ scrimBehindAlpha: Float,
+ scrimInFrontColor: ColorExtractor.GradientColors,
+ )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
index a33996b99900..edc1f88b7579 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarControllerImpl.java
@@ -11,7 +11,7 @@
* 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
+ * limitations under the License.
*/
package com.android.systemui.statusbar.phone;
@@ -22,9 +22,9 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
+import android.view.Display;
import android.view.InsetsFlags;
import android.view.ViewDebug;
import android.view.WindowInsetsController.Appearance;
@@ -34,30 +34,32 @@ import androidx.annotation.Nullable;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
import com.android.internal.view.AppearanceRegion;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.Dumpable;
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.statusbar.data.model.StatusBarAppearance;
-import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Compile;
-import com.android.systemui.util.kotlin.JavaAdapter;
+import com.android.systemui.util.kotlin.JavaAdapterKt;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+import kotlin.coroutines.CoroutineContext;
+
+import kotlinx.coroutines.CoroutineScope;
import java.io.PrintWriter;
import java.util.ArrayList;
-import javax.inject.Inject;
-
/**
* Controls how light status bar flag applies to the icons.
*/
-@SysUISingleton
-public class LightBarController implements
- BatteryController.BatteryStateChangeCallback, Dumpable, CoreStartable {
+public class LightBarControllerImpl implements
+ BatteryController.BatteryStateChangeCallback, LightBarController {
private static final String TAG = "LightBarController";
private static final boolean DEBUG_NAVBAR = Compile.IS_DEBUG;
@@ -65,11 +67,14 @@ public class LightBarController implements
private static final float NAV_BAR_INVERSION_SCRIM_ALPHA_THRESHOLD = 0.1f;
- private final JavaAdapter mJavaAdapter;
+ private final CoroutineScope mCoroutineScope;
private final SysuiDarkIconDispatcher mStatusBarIconController;
private final BatteryController mBatteryController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private BiometricUnlockController mBiometricUnlockController;
+ private final NavigationModeController mNavModeController;
+ private final DumpManager mDumpManager;
+ private final StatusBarModePerDisplayRepository mStatusBarModeRepository;
+ private final CoroutineContext mMainContext;
+ private final BiometricUnlockController mBiometricUnlockController;
private LightBarTransitionsController mNavigationBarController;
private @Appearance int mAppearance;
@@ -119,47 +124,60 @@ public class LightBarController implements
private String mLastNavigationBarAppearanceChangedLog;
private StringBuilder mLogStringBuilder = null;
- @Inject
- public LightBarController(
- Context ctx,
- JavaAdapter javaAdapter,
+ private final String mDumpableName;
+
+ private final NavigationModeController.ModeChangedListener mNavigationModeListener =
+ (mode) -> mNavigationMode = mode;
+
+ @AssistedInject
+ public LightBarControllerImpl(
+ @Assisted int displayId,
+ @Assisted CoroutineScope coroutineScope,
DarkIconDispatcher darkIconDispatcher,
BatteryController batteryController,
NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
+ @Assisted StatusBarModePerDisplayRepository statusBarModeRepository,
DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
+ @Main CoroutineContext mainContext,
+ BiometricUnlockController biometricUnlockController) {
+ mCoroutineScope = coroutineScope;
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
- mBatteryController.addCallback(this);
+ mNavModeController = navModeController;
+ mDumpManager = dumpManager;
mStatusBarModeRepository = statusBarModeRepository;
- mNavigationMode = navModeController.addListener((mode) -> {
- mNavigationMode = mode;
- });
-
- if (ctx.getDisplayId() == displayTracker.getDefaultDisplayId()) {
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- }
+ mMainContext = mainContext;
+ mBiometricUnlockController = biometricUnlockController;
+ String dumpableNameSuffix =
+ displayId == Display.DEFAULT_DISPLAY ? "" : String.valueOf(displayId);
+ mDumpableName = getClass().getSimpleName() + dumpableNameSuffix;
}
@Override
public void start() {
- mJavaAdapter.alwaysCollectFlow(
- mStatusBarModeRepository.getDefaultDisplay().getStatusBarAppearance(),
+ mDumpManager.registerCriticalDumpable(mDumpableName, this);
+ mBatteryController.addCallback(this);
+ mNavigationMode = mNavModeController.addListener(mNavigationModeListener);
+ JavaAdapterKt.collectFlow(
+ mCoroutineScope,
+ mMainContext,
+ mStatusBarModeRepository.getStatusBarAppearance(),
this::onStatusBarAppearanceChanged);
}
+ @Override
+ public void stop() {
+ mDumpManager.unregisterDumpable(mDumpableName);
+ mBatteryController.removeCallback(this);
+ mNavModeController.removeListener(mNavigationModeListener);
+ }
+
+ @Override
public void setNavigationBar(LightBarTransitionsController navigationBar) {
mNavigationBarController = navigationBar;
updateNavigation();
}
- public void setBiometricUnlockController(
- BiometricUnlockController biometricUnlockController) {
- mBiometricUnlockController = biometricUnlockController;
- }
-
private void onStatusBarAppearanceChanged(@Nullable StatusBarAppearance params) {
if (params == null) {
return;
@@ -202,6 +220,7 @@ public class LightBarController implements
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarAppearanceChanged(@Appearance int appearance, boolean nbModeChanged,
int navigationBarMode, boolean navbarColorManagedByIme) {
int diff = appearance ^ mAppearance;
@@ -244,6 +263,7 @@ public class LightBarController implements
mNavbarColorManagedByIme = navbarColorManagedByIme;
}
+ @Override
public void onNavigationBarModeChanged(int newBarMode) {
mHasLightNavigationBar = isLight(mAppearance, newBarMode, APPEARANCE_LIGHT_NAVIGATION_BARS);
}
@@ -258,30 +278,28 @@ public class LightBarController implements
mNavigationBarMode, mNavbarColorManagedByIme);
}
+ @Override
public void setQsCustomizing(boolean customizing) {
if (mQsCustomizing == customizing) return;
mQsCustomizing = customizing;
reevaluate();
}
- /** Set if Quick Settings is fully expanded, which affects notification scrim visibility */
+ @Override
public void setQsExpanded(boolean expanded) {
if (mQsExpanded == expanded) return;
mQsExpanded = expanded;
reevaluate();
}
- /** Set if Global Actions dialog is visible, which requires dark mode (light buttons) */
+ @Override
public void setGlobalActionsVisible(boolean visible) {
if (mGlobalActionsVisible == visible) return;
mGlobalActionsVisible = visible;
reevaluate();
}
- /**
- * Controls the light status bar temporarily for back navigation.
- * @param appearance the custmoized appearance.
- */
+ @Override
public void customizeStatusBarAppearance(AppearanceRegion appearance) {
if (appearance != null) {
final ArrayList<AppearanceRegion> appearancesList = new ArrayList<>();
@@ -303,16 +321,14 @@ public class LightBarController implements
}
}
- /**
- * Sets whether the direct-reply is in use or not.
- * @param directReplying {@code true} when the direct-reply is in-use.
- */
+ @Override
public void setDirectReplying(boolean directReplying) {
if (mDirectReplying == directReplying) return;
mDirectReplying = directReplying;
reevaluate();
}
+ @Override
public void setScrimState(ScrimState scrimState, float scrimBehindAlpha,
GradientColors scrimInFrontColor) {
boolean bouncerVisibleLast = mBouncerVisible;
@@ -368,9 +384,6 @@ public class LightBarController implements
}
private boolean animateChange() {
- if (mBiometricUnlockController == null) {
- return false;
- }
int unlockMode = mBiometricUnlockController.getMode();
return unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
&& unlockMode != BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -387,20 +400,17 @@ public class LightBarController implements
}
}
- // If no one is light, all icons become white.
if (lightBarBounds.isEmpty()) {
- mStatusBarIconController.getTransitionsController().setIconsDark(
- false, animateChange());
- }
-
- // If all stacks are light, all icons get dark.
- else if (lightBarBounds.size() == numStacks) {
+ // If no one is light, all icons become white.
+ mStatusBarIconController
+ .getTransitionsController()
+ .setIconsDark(false, animateChange());
+ } else if (lightBarBounds.size() == numStacks) {
+ // If all stacks are light, all icons get dark.
mStatusBarIconController.setIconsDarkArea(null);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
- }
-
- // Not the same for every stack, magic!
- else {
+ } else {
+ // Not the same for every stack, magic!
mStatusBarIconController.setIconsDarkArea(lightBarBounds);
mStatusBarIconController.getTransitionsController().setIconsDark(true, animateChange());
}
@@ -468,47 +478,15 @@ public class LightBarController implements
}
}
- /**
- * Injectable factory for creating a {@link LightBarController}.
- */
- public static class Factory {
- private final JavaAdapter mJavaAdapter;
- private final DarkIconDispatcher mDarkIconDispatcher;
- private final BatteryController mBatteryController;
- private final NavigationModeController mNavModeController;
- private final StatusBarModeRepositoryStore mStatusBarModeRepository;
- private final DumpManager mDumpManager;
- private final DisplayTracker mDisplayTracker;
-
- @Inject
- public Factory(
- JavaAdapter javaAdapter,
- DarkIconDispatcher darkIconDispatcher,
- BatteryController batteryController,
- NavigationModeController navModeController,
- StatusBarModeRepositoryStore statusBarModeRepository,
- DumpManager dumpManager,
- DisplayTracker displayTracker) {
- mJavaAdapter = javaAdapter;
- mDarkIconDispatcher = darkIconDispatcher;
- mBatteryController = batteryController;
- mNavModeController = navModeController;
- mStatusBarModeRepository = statusBarModeRepository;
- mDumpManager = dumpManager;
- mDisplayTracker = displayTracker;
- }
+ /** Injectable factory for creating a {@link LightBarControllerImpl}. */
+ @AssistedFactory
+ @FunctionalInterface
+ public interface Factory {
- /** Create an {@link LightBarController} */
- public LightBarController create(Context context) {
- return new LightBarController(
- context,
- mJavaAdapter,
- mDarkIconDispatcher,
- mBatteryController,
- mNavModeController,
- mStatusBarModeRepository,
- mDumpManager,
- mDisplayTracker);
- }
+ /** Creates a {@link LightBarControllerImpl}. */
+ LightBarControllerImpl create(
+ int displayId,
+ CoroutineScope coroutineScope,
+ StatusBarModePerDisplayRepository statusBarModePerDisplayRepository);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 91c43ddf1ce4..176dd8de6cd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -39,8 +39,8 @@ import com.android.systemui.Dependency;
import com.android.systemui.Flags;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.res.R;
-import com.android.systemui.shade.LongPressGestureDetector;
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.window.StatusBarWindowControllerStore;
import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
@@ -69,7 +69,7 @@ public class PhoneStatusBarView extends FrameLayout {
private InsetsFetcher mInsetsFetcher;
private int mDensity;
private float mFontScale;
- private LongPressGestureDetector mLongPressGestureDetector;
+ private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
/**
* Draw this many pixels into the left/right side of the cutout to optimally use the space
@@ -81,9 +81,10 @@ public class PhoneStatusBarView extends FrameLayout {
mStatusBarWindowControllerStore = Dependency.get(StatusBarWindowControllerStore.class);
}
- void setLongPressGestureDetector(LongPressGestureDetector longPressGestureDetector) {
+ void setLongPressGestureDetector(
+ StatusBarLongPressGestureDetector statusBarLongPressGestureDetector) {
if (ShadeExpandsOnStatusBarLongPress.isEnabled()) {
- mLongPressGestureDetector = longPressGestureDetector;
+ mStatusBarLongPressGestureDetector = statusBarLongPressGestureDetector;
}
}
@@ -207,8 +208,9 @@ public class PhoneStatusBarView extends FrameLayout {
@Override
public boolean onTouchEvent(MotionEvent event) {
- if (ShadeExpandsOnStatusBarLongPress.isEnabled() && mLongPressGestureDetector != null) {
- mLongPressGestureDetector.handleTouch(event);
+ if (ShadeExpandsOnStatusBarLongPress.isEnabled()
+ && mStatusBarLongPressGestureDetector != null) {
+ mStatusBarLongPressGestureDetector.handleTouch(event);
}
if (mTouchEventHandler == null) {
Log.w(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index c24f4327f471..424549453af3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -33,11 +33,11 @@ import com.android.systemui.plugins.DarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeExpandsOnStatusBarLongPress
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.data.repository.StatusBarContentInsetsProviderStore
@@ -68,7 +68,7 @@ private constructor(
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
@@ -118,7 +118,7 @@ private constructor(
addCursorSupportToIconContainers()
if (ShadeExpandsOnStatusBarLongPress.isEnabled) {
- mView.setLongPressGestureDetector(longPressGestureDetector.get())
+ mView.setLongPressGestureDetector(statusBarLongPressGestureDetector.get())
}
progressProvider?.setReadyToHandleTransition(true)
@@ -335,7 +335,7 @@ private constructor(
private val shadeController: ShadeController,
private val shadeViewController: ShadeViewController,
private val panelExpansionInteractor: PanelExpansionInteractor,
- private val longPressGestureDetector: Provider<LongPressGestureDetector>,
+ private val statusBarLongPressGestureDetector: Provider<StatusBarLongPressGestureDetector>,
private val windowRootView: Provider<WindowRootView>,
private val shadeLogger: ShadeLogger,
private val viewUtil: ViewUtil,
@@ -360,7 +360,7 @@ private constructor(
shadeController,
shadeViewController,
panelExpansionInteractor,
- longPressGestureDetector,
+ statusBarLongPressGestureDetector,
windowRootView,
shadeLogger,
statusBarMoveFromCenterAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
index 94de3510188c..ba878edc1132 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt
@@ -103,8 +103,12 @@ interface StatusBarPhoneModule {
fun statusBarInitializerImpl(
implFactory: StatusBarInitializerImpl.Factory,
statusBarWindowControllerStore: StatusBarWindowControllerStore,
+ statusBarModeRepositoryStore: StatusBarModeRepositoryStore,
): StatusBarInitializerImpl {
- return implFactory.create(statusBarWindowControllerStore.defaultDisplay)
+ return implFactory.create(
+ statusBarWindowControllerStore.defaultDisplay,
+ statusBarModeRepositoryStore.defaultDisplay,
+ )
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
index 1d08f2ba9522..98eed848bab1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepository.kt
@@ -37,6 +37,9 @@ interface DeviceBasedSatelliteRepository {
/** Clients must observe this property, as device-based satellite is location-dependent */
val isSatelliteAllowedForCurrentLocation: StateFlow<Boolean>
+
+ /** When enabled, a satellite icon will display when all other connections are OOS */
+ val isOpportunisticSatelliteIconEnabled: Boolean
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
index 58c30e018efd..de42b9229715 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/DeviceBasedSatelliteRepositorySwitcher.kt
@@ -97,6 +97,9 @@ constructor(
}
.stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+ override val isOpportunisticSatelliteIconEnabled: Boolean
+ get() = activeRepo.value.isOpportunisticSatelliteIconEnabled
+
override val isSatelliteProvisioned: StateFlow<Boolean> =
activeRepo
.flatMapLatest { it.isSatelliteProvisioned }
@@ -118,6 +121,6 @@ constructor(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- realImpl.isSatelliteAllowedForCurrentLocation.value
+ realImpl.isSatelliteAllowedForCurrentLocation.value,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
index d557bbf306a6..755899f80928 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/demo/DemoDeviceBasedSatelliteRepository.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.pipeline.satellite.data.demo
+import android.content.res.Resources
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
-import com.android.app.tracing.coroutines.launchTraced as launch
/** A satellite repository that represents the latest satellite values sent via demo mode. */
@SysUISingleton
@@ -33,9 +36,13 @@ class DemoDeviceBasedSatelliteRepository
constructor(
private val dataSource: DemoDeviceBasedSatelliteDataSource,
@Application private val scope: CoroutineScope,
+ @Main resources: Resources,
) : DeviceBasedSatelliteRepository {
private var demoCommandJob: Job? = null
+ override val isOpportunisticSatelliteIconEnabled =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
override val isSatelliteProvisioned = MutableStateFlow(true)
override val connectionState = MutableStateFlow(SatelliteConnectionState.Unknown)
override val signalStrength = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 7686338fd9eb..a36ef56e57a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.pipeline.satellite.data.prod
+import android.content.res.Resources
import android.os.OutcomeReceiver
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
@@ -27,14 +28,17 @@ import android.telephony.satellite.SatelliteModemStateCallback
import android.telephony.satellite.SatelliteProvisionStateCallback
import android.telephony.satellite.SatelliteSupportedStateCallback
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.LogLevel
import com.android.systemui.log.core.MessageInitializer
import com.android.systemui.log.core.MessagePrinter
+import com.android.systemui.res.R
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
@@ -66,7 +70,6 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
@@ -146,10 +149,14 @@ constructor(
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@VerboseDeviceBasedSatelliteInputLog private val verboseLogBuffer: LogBuffer,
private val systemClock: SystemClock,
+ @Main resources: Resources,
) : RealDeviceBasedSatelliteRepository {
private val satelliteManager: SatelliteManager?
+ override val isOpportunisticSatelliteIconEnabled: Boolean =
+ resources.getBoolean(R.bool.config_showOpportunisticSatelliteIcon)
+
// Some calls into satellite manager will throw exceptions if it is not supported.
// This is never expected to change after boot, but may need to be retried in some cases
@get:VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
index f1a444f0158a..08a98c397d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/domain/interactor/DeviceBasedSatelliteInteractor.kt
@@ -53,6 +53,9 @@ constructor(
@DeviceBasedSatelliteInputLog private val logBuffer: LogBuffer,
@DeviceBasedSatelliteTableLog private val tableLog: TableLogBuffer,
) {
+ /** Whether or not we should show the satellite icon when all connections are OOS */
+ val isOpportunisticSatelliteIconEnabled = repo.isOpportunisticSatelliteIconEnabled
+
/** Must be observed by any UI showing Satellite iconography */
val isSatelliteAllowed =
if (Flags.oemEnabledSatelliteFlag()) {
@@ -93,12 +96,7 @@ constructor(
flowOf(0)
}
.distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "",
- columnName = COL_LEVEL,
- initialValue = 0,
- )
+ .logDiffsForTable(tableLog, columnPrefix = "", columnName = COL_LEVEL, initialValue = 0)
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
val isSatelliteProvisioned = repo.isSatelliteProvisioned
@@ -132,10 +130,9 @@ constructor(
/** When all connections are considered OOS, satellite connectivity is potentially valid */
val areAllConnectionsOutOfService =
if (Flags.oemEnabledSatelliteFlag()) {
- combine(
- allConnectionsOos,
- iconsInteractor.isDeviceInEmergencyCallsOnlyMode,
- ) { connectionsOos, deviceEmergencyOnly ->
+ combine(allConnectionsOos, iconsInteractor.isDeviceInEmergencyCallsOnlyMode) {
+ connectionsOos,
+ deviceEmergencyOnly ->
logBuffer.log(
TAG,
LogLevel.INFO,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
index 37f2f195ebf6..13ac321473f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
@@ -77,35 +77,38 @@ constructor(
// This adds a 10 seconds delay before showing the icon
private val shouldShowIconForOosAfterHysteresis: StateFlow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { shouldShow ->
- if (shouldShow) {
- logBuffer.log(
- TAG,
- LogLevel.INFO,
- { long1 = DELAY_DURATION.inWholeSeconds },
- { "Waiting $long1 seconds before showing the satellite icon" }
+ if (interactor.isOpportunisticSatelliteIconEnabled) {
+ interactor.areAllConnectionsOutOfService
+ .flatMapLatest { shouldShow ->
+ if (shouldShow) {
+ logBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ { long1 = DELAY_DURATION.inWholeSeconds },
+ { "Waiting $long1 seconds before showing the satellite icon" },
+ )
+ delay(DELAY_DURATION)
+ flowOf(true)
+ } else {
+ flowOf(false)
+ }
+ }
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ tableLog,
+ columnPrefix = "vm",
+ columnName = COL_VISIBLE_FOR_OOS,
+ initialValue = false,
)
- delay(DELAY_DURATION)
- flowOf(true)
- } else {
- flowOf(false)
- }
+ } else {
+ flowOf(false)
}
- .distinctUntilChanged()
- .logDiffsForTable(
- tableLog,
- columnPrefix = "vm",
- columnName = COL_VISIBLE_FOR_OOS,
- initialValue = false,
- )
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
private val canShowIcon =
- combine(
- interactor.isSatelliteAllowed,
- interactor.isSatelliteProvisioned,
- ) { allowed, provisioned ->
+ combine(interactor.isSatelliteAllowed, interactor.isSatelliteProvisioned) {
+ allowed,
+ provisioned ->
allowed && provisioned
}
@@ -141,11 +144,10 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), false)
override val icon: StateFlow<Icon?> =
- combine(
- showIcon,
- interactor.connectionState,
- interactor.signalStrength,
- ) { shouldShow, state, signalStrength ->
+ combine(showIcon, interactor.connectionState, interactor.signalStrength) {
+ shouldShow,
+ state,
+ signalStrength ->
if (shouldShow) {
SatelliteIconModel.fromConnectionState(state, signalStrength)
} else {
@@ -155,10 +157,7 @@ constructor(
.stateIn(scope, SharingStarted.WhileSubscribed(), null)
override val carrierText: StateFlow<String?> =
- combine(
- showIcon,
- interactor.connectionState,
- ) { shouldShow, connectionState ->
+ combine(showIcon, interactor.connectionState) { shouldShow, connectionState ->
logBuffer.log(
TAG,
LogLevel.INFO,
@@ -166,7 +165,7 @@ constructor(
bool1 = shouldShow
str1 = connectionState.name
},
- { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" }
+ { "Updating carrier text. shouldShow=$bool1 connectionState=$str1" },
)
if (shouldShow) {
when (connectionState) {
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 9c8ef0421888..1c3fece1beb1 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -242,10 +242,16 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
}
};
- private int getLatestWallpaperType(int userId) {
- return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
- > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
- ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ private int getDefaultWallpaperColorsSource(int userId) {
+ if (com.android.systemui.shared.Flags.newCustomizationPickerUi()) {
+ // The wallpaper colors source is always the home wallpaper.
+ return WallpaperManager.FLAG_SYSTEM;
+ } else {
+ // The wallpaper colors source is based on the last set wallpaper.
+ return mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_LOCK, userId)
+ > mWallpaperManager.getWallpaperIdForUser(WallpaperManager.FLAG_SYSTEM, userId)
+ ? WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM;
+ }
}
private boolean isSeedColorSet(JSONObject jsonObject, WallpaperColors newWallpaperColors) {
@@ -279,9 +285,9 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
private void handleWallpaperColors(WallpaperColors wallpaperColors, int flags, int userId) {
final int currentUser = mUserTracker.getUserId();
final boolean hadWallpaperColors = mCurrentColors.get(userId) != null;
- int latestWallpaperType = getLatestWallpaperType(userId);
- boolean eventForLatestWallpaper = (flags & latestWallpaperType) != 0;
- if (eventForLatestWallpaper) {
+ int wallpaperColorsSource = getDefaultWallpaperColorsSource(userId);
+ boolean wallpaperColorsNeedUpdate = (flags & wallpaperColorsSource) != 0;
+ if (wallpaperColorsNeedUpdate) {
mCurrentColors.put(userId, wallpaperColors);
if (DEBUG) Log.d(TAG, "got new colors: " + wallpaperColors + " where: " + flags);
}
@@ -328,7 +334,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
boolean userChoseLockScreenColor = COLOR_SOURCE_LOCK.equals(wallpaperPickerColorSource);
boolean preserveLockScreenColor = isDestinationHomeOnly && userChoseLockScreenColor;
- if (!userChosePresetColor && !preserveLockScreenColor && eventForLatestWallpaper
+ if (!userChosePresetColor && !preserveLockScreenColor && wallpaperColorsNeedUpdate
&& !isSeedColorSet(jsonObject, wallpaperColors)) {
mSkipSettingChange = true;
if (jsonObject.has(OVERLAY_CATEGORY_ACCENT_COLOR) || jsonObject.has(
@@ -494,7 +500,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable {
// Upon boot, make sure we have the most up to date colors
Runnable updateColors = () -> {
WallpaperColors systemColor = mWallpaperManager.getWallpaperColors(
- getLatestWallpaperType(mUserTracker.getUserId()));
+ getDefaultWallpaperColorsSource(mUserTracker.getUserId()));
Runnable applyColors = () -> {
if (DEBUG) Log.d(TAG, "Boot colors: " + systemColor);
mCurrentColors.put(mUserTracker.getUserId(), systemColor);
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
index 3662c78efb16..163288b25b28 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractor.kt
@@ -32,6 +32,7 @@ import android.os.UserHandle
import android.os.UserManager
import android.provider.Settings
import android.util.Log
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.internal.logging.UiEventLogger
import com.android.internal.util.UserIcons
import com.android.keyguard.KeyguardUpdateMonitor
@@ -81,7 +82,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
@@ -109,7 +109,7 @@ constructor(
private val guestUserInteractor: GuestUserInteractor,
private val uiEventLogger: UiEventLogger,
private val userRestrictionChecker: UserRestrictionChecker,
- private val processWrapper: ProcessWrapper
+ private val processWrapper: ProcessWrapper,
) {
/**
* Defines interface for classes that can be notified when the state of users on the device is
@@ -137,11 +137,10 @@ constructor(
/** List of current on-device users to select from. */
val users: Flow<List<UserModel>>
get() =
- combine(
+ combine(userInfos, repository.selectedUserInfo, repository.userSwitcherSettings) {
userInfos,
- repository.selectedUserInfo,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, settings ->
+ selectedUserInfo,
+ settings ->
toUserModels(
userInfos = userInfos,
selectedUserId = selectedUserInfo.id,
@@ -157,7 +156,7 @@ constructor(
toUserModel(
userInfo = selectedUserInfo,
selectedUserId = selectedUserId,
- canSwitchUsers = canSwitchUsers(selectedUserId)
+ canSwitchUsers = canSwitchUsers(selectedUserId),
)
}
@@ -211,7 +210,7 @@ constructor(
manager,
repository,
settings.isUserSwitcherEnabled,
- canAccessUserSwitcher
+ canAccessUserSwitcher,
)
if (canCreateUsers) {
@@ -238,7 +237,7 @@ constructor(
if (
UserActionsUtil.canManageUsers(
repository,
- settings.isUserSwitcherEnabled
+ settings.isUserSwitcherEnabled,
)
) {
add(UserActionModel.NAVIGATE_TO_USER_MANAGEMENT)
@@ -248,18 +247,14 @@ constructor(
.flowOn(backgroundDispatcher)
val userRecords: StateFlow<ArrayList<UserRecord>> =
- combine(
+ combine(userInfos, repository.selectedUserInfo, actions, repository.userSwitcherSettings) {
userInfos,
- repository.selectedUserInfo,
- actions,
- repository.userSwitcherSettings,
- ) { userInfos, selectedUserInfo, actionModels, settings ->
+ selectedUserInfo,
+ actionModels,
+ settings ->
ArrayList(
userInfos.map {
- toRecord(
- userInfo = it,
- selectedUserId = selectedUserInfo.id,
- )
+ toRecord(userInfo = it, selectedUserId = selectedUserInfo.id)
} +
actionModels.map {
toRecord(
@@ -298,7 +293,8 @@ constructor(
val isGuestUserResetting: Boolean = guestUserInteractor.isGuestUserResetting
/** Whether to enable the user chip in the status bar */
- val isStatusBarUserChipEnabled: Boolean = repository.isStatusBarUserChipEnabled
+ val isStatusBarUserChipEnabled: Boolean
+ get() = repository.isStatusBarUserChipEnabled
private val _dialogShowRequests = MutableStateFlow<ShowDialogRequestModel?>(null)
val dialogShowRequests: Flow<ShowDialogRequestModel?> = _dialogShowRequests.asStateFlow()
@@ -467,10 +463,8 @@ constructor(
when (action) {
UserActionModel.ENTER_GUEST_MODE -> {
uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
- guestUserInteractor.createAndSwitchTo(
- this::showDialog,
- this::dismissDialog,
- ) { userId ->
+ guestUserInteractor.createAndSwitchTo(this::showDialog, this::dismissDialog) {
+ userId ->
selectUser(userId, dialogShower)
}
}
@@ -481,7 +475,7 @@ constructor(
activityStarter.startActivity(
CreateUserActivity.createIntentForStart(
applicationContext,
- keyguardInteractor.isKeyguardShowing()
+ keyguardInteractor.isKeyguardShowing(),
),
/* dismissShade= */ true,
/* animationController */ null,
@@ -523,17 +517,14 @@ constructor(
)
}
- fun removeGuestUser(
- @UserIdInt guestUserId: Int,
- @UserIdInt targetUserId: Int,
- ) {
+ fun removeGuestUser(@UserIdInt guestUserId: Int, @UserIdInt targetUserId: Int) {
applicationScope.launch {
guestUserInteractor.remove(
guestUserId = guestUserId,
targetUserId = targetUserId,
::showDialog,
::dismissDialog,
- ::switchUser
+ ::switchUser,
)
}
}
@@ -570,10 +561,7 @@ constructor(
}
}
- private suspend fun toRecord(
- userInfo: UserInfo,
- selectedUserId: Int,
- ): UserRecord {
+ private suspend fun toRecord(userInfo: UserInfo, selectedUserId: Int): UserRecord {
return LegacyUserDataHelper.createRecord(
context = applicationContext,
manager = manager,
@@ -595,10 +583,7 @@ constructor(
actionType = action,
isRestricted = isRestricted,
isSwitchToEnabled =
- canSwitchUsers(
- selectedUserId = selectedUserId,
- isAction = true,
- ) &&
+ canSwitchUsers(selectedUserId = selectedUserId, isAction = true) &&
// If the user is auto-created is must not be currently resetting.
!(isGuestUserAutoCreated && isGuestUserResetting),
userRestrictionChecker = userRestrictionChecker,
@@ -623,10 +608,7 @@ constructor(
}
}
- private suspend fun onBroadcastReceived(
- intent: Intent,
- previousUserInfo: UserInfo?,
- ) {
+ private suspend fun onBroadcastReceived(intent: Intent, previousUserInfo: UserInfo?) {
val shouldRefreshAllUsers =
when (intent.action) {
Intent.ACTION_LOCALE_CHANGED -> true
@@ -645,10 +627,8 @@ constructor(
Intent.ACTION_USER_INFO_CHANGED -> true
Intent.ACTION_USER_UNLOCKED -> {
// If we unlocked the system user, we should refresh all users.
- intent.getIntExtra(
- Intent.EXTRA_USER_HANDLE,
- UserHandle.USER_NULL,
- ) == UserHandle.USER_SYSTEM
+ intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL) ==
+ UserHandle.USER_SYSTEM
}
else -> true
}
@@ -668,20 +648,14 @@ constructor(
// Disconnect from the old secondary user's service
val secondaryUserId = repository.secondaryUserId
if (secondaryUserId != UserHandle.USER_NULL) {
- applicationContext.stopServiceAsUser(
- intent,
- UserHandle.of(secondaryUserId),
- )
+ applicationContext.stopServiceAsUser(intent, UserHandle.of(secondaryUserId))
repository.secondaryUserId = UserHandle.USER_NULL
}
// Connect to the new secondary user's service (purely to ensure that a persistent
// SystemUI application is created for that user)
if (userId != processWrapper.myUserHandle().identifier && !processWrapper.isSystemUser) {
- applicationContext.startServiceAsUser(
- intent,
- UserHandle.of(userId),
- )
+ applicationContext.startServiceAsUser(intent, UserHandle.of(userId))
repository.secondaryUserId = userId
}
}
@@ -732,7 +706,7 @@ constructor(
private suspend fun toUserModel(
userInfo: UserInfo,
selectedUserId: Int,
- canSwitchUsers: Boolean
+ canSwitchUsers: Boolean,
): UserModel {
val userId = userInfo.id
val isSelected = userId == selectedUserId
@@ -740,11 +714,7 @@ constructor(
UserModel(
id = userId,
name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = true,
- userId = userId,
- ),
+ image = getUserImage(isGuest = true, userId = userId),
isSelected = isSelected,
isSelectable = canSwitchUsers,
isGuest = true,
@@ -753,11 +723,7 @@ constructor(
UserModel(
id = userId,
name = Text.Loaded(userInfo.name),
- image =
- getUserImage(
- isGuest = false,
- userId = userId,
- ),
+ image = getUserImage(isGuest = false, userId = userId),
isSelected = isSelected,
isSelectable = canSwitchUsers || isSelected,
isGuest = false,
@@ -765,10 +731,7 @@ constructor(
}
}
- private suspend fun canSwitchUsers(
- selectedUserId: Int,
- isAction: Boolean = false,
- ): Boolean {
+ private suspend fun canSwitchUsers(selectedUserId: Int, isAction: Boolean = false): Boolean {
val isHeadlessSystemUserMode =
withContext(backgroundDispatcher) { headlessSystemUserMode.isHeadlessSystemUserMode() }
// Whether menu item should be active. True if item is a user or if any user has
@@ -785,7 +748,7 @@ constructor(
.getUsers(
/* excludePartial= */ true,
/* excludeDying= */ true,
- /* excludePreCreated= */ true
+ /* excludePreCreated= */ true,
)
.any { user ->
user.id != UserHandle.USER_SYSTEM &&
@@ -794,10 +757,7 @@ constructor(
}
@SuppressLint("UseCompatLoadingForDrawables")
- private suspend fun getUserImage(
- isGuest: Boolean,
- userId: Int,
- ): Drawable {
+ private suspend fun getUserImage(isGuest: Boolean, userId: Int): Drawable {
if (isGuest) {
return checkNotNull(
applicationContext.getDrawable(com.android.settingslib.R.drawable.ic_account_circle)
@@ -823,13 +783,13 @@ constructor(
return UserIcons.getDefaultUserIcon(
applicationContext.resources,
userId,
- /* light= */ false
+ /* light= */ false,
)
}
private fun canCreateGuestUser(
settings: UserSwitcherSettingsModel,
- canAccessUserSwitcher: Boolean
+ canAccessUserSwitcher: Boolean,
): Boolean {
return guestUserInteractor.isGuestUserAutoCreated ||
UserActionsUtil.canCreateGuest(
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
index 2c425b199b4e..53c2d888922b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModel.kt
@@ -30,11 +30,10 @@ import kotlinx.coroutines.flow.mapLatest
@OptIn(ExperimentalCoroutinesApi::class)
class StatusBarUserChipViewModel
@Inject
-constructor(
- interactor: UserSwitcherInteractor,
-) {
+constructor(private val interactor: UserSwitcherInteractor) {
/** Whether the status bar chip ui should be available */
- val chipEnabled: Boolean = interactor.isStatusBarUserChipEnabled
+ val chipEnabled: Boolean
+ get() = interactor.isStatusBarUserChipEnabled
/** Whether or not the chip should be showing, based on the number of users */
val isChipVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 315912406b6d..63a5b3f1e6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -20,6 +20,7 @@ import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,7 +36,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.plus
/** A class allowing Java classes to collect on Kotlin flows. */
@SysUISingleton
@@ -102,6 +103,22 @@ fun <T> collectFlow(
}
}
+/**
+ * Collect information for the given [flow], calling [consumer] for each emitted event on the
+ * specified [collectContext].
+ *
+ * Collection will continue until the given [scope] is cancelled.
+ */
+@JvmOverloads
+fun <T> collectFlow(
+ scope: CoroutineScope,
+ collectContext: CoroutineContext = scope.coroutineContext,
+ flow: Flow<T>,
+ consumer: Consumer<T>,
+): Job {
+ return scope.plus(collectContext).launch { flow.collect { consumer.accept(it) } }
+}
+
fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
return combine(flow1, flow2, bifunction)
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
index d509b2da482e..f36c335e0f44 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsUtilModule.java
@@ -16,9 +16,6 @@
package com.android.systemui.util.settings;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository;
-import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepositoryImpl;
-
import dagger.Binds;
import dagger.Module;
@@ -39,9 +36,4 @@ public interface SettingsUtilModule {
/** Bind GlobalSettingsImpl to GlobalSettings. */
@Binds
GlobalSettings bindsGlobalSettings(GlobalSettingsImpl impl);
-
- /** Bind UserAwareSecureSettingsRepositoryImpl to UserAwareSecureSettingsRepository. */
- @Binds
- UserAwareSecureSettingsRepository bindsUserAwareSecureSettingsRepository(
- UserAwareSecureSettingsRepositoryImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
index d3e50803b5d5..71335ec84c86 100644
--- a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSecureSettingsRepository.kt
@@ -19,52 +19,25 @@ package com.android.systemui.util.settings.repository
import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.shared.settings.data.repository.SecureSettingsRepository
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
-import com.android.systemui.util.settings.SettingsProxy
-import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOn
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onStart
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
/**
* Repository for observing values of [Settings.Secure] for the currently active user. That means
* when user is switched and the new user has different value, flow will emit new value.
*/
-interface UserAwareSecureSettingsRepository {
-
- /**
- * Emits boolean value of the setting for active user. Also emits starting value when
- * subscribed.
- * See: [SettingsProxy.getBool].
- */
- fun boolSettingForActiveUser(name: String, defaultValue: Boolean = false): Flow<Boolean>
-}
-
@SysUISingleton
-@OptIn(ExperimentalCoroutinesApi::class)
-class UserAwareSecureSettingsRepositoryImpl @Inject constructor(
- private val secureSettings: SecureSettings,
- private val userRepository: UserRepository,
- @Background private val backgroundDispatcher: CoroutineDispatcher,
-) : UserAwareSecureSettingsRepository {
-
- override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo -> settingObserver(name, defaultValue, userInfo.id) }
- .distinctUntilChanged()
- .flowOn(backgroundDispatcher)
-
- private fun settingObserver(name: String, defaultValue: Boolean, userId: Int): Flow<Boolean> {
- return secureSettings
- .observerFlow(userId, name)
- .onStart { emit(Unit) }
- .map { secureSettings.getBoolForUser(name, defaultValue, userId) }
- }
-} \ No newline at end of file
+class UserAwareSecureSettingsRepository
+@Inject
+constructor(
+ secureSettings: SecureSettings,
+ userRepository: UserRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background bgContext: CoroutineContext,
+) :
+ UserAwareSettingsRepository(secureSettings, userRepository, backgroundDispatcher, bgContext),
+ SecureSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
new file mode 100644
index 000000000000..a31b8d943a1b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSettingsRepository.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.util.settings.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import com.android.systemui.util.settings.UserSettingsProxy
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/**
+ * Repository for observing values of a [UserSettingsProxy], for the currently active user. That
+ * means that when user is switched and the new user has a different value, the flow will emit the
+ * new value.
+ */
+@SysUISingleton
+@OptIn(ExperimentalCoroutinesApi::class)
+abstract class UserAwareSettingsRepository(
+ private val userSettings: UserSettingsProxy,
+ private val userRepository: UserRepository,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ @Background private val bgContext: CoroutineContext,
+) {
+
+ fun boolSetting(name: String, defaultValue: Boolean): Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ settingObserver(name, userInfo.id) {
+ userSettings.getBoolForUser(name, defaultValue, userInfo.id)
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+
+ fun intSetting(name: String, defaultValue: Int): Flow<Int> {
+ return userRepository.selectedUserInfo
+ .flatMapLatest { userInfo ->
+ settingObserver(name, userInfo.id) {
+ userSettings.getIntForUser(name, defaultValue, userInfo.id)
+ }
+ }
+ .distinctUntilChanged()
+ .flowOn(backgroundDispatcher)
+ }
+
+ private fun <T> settingObserver(name: String, userId: Int, settingsReader: () -> T): Flow<T> {
+ return userSettings
+ .observerFlow(userId, name)
+ .onStart { emit(Unit) }
+ .map { settingsReader.invoke() }
+ }
+
+ suspend fun setInt(name: String, value: Int) {
+ withContext(bgContext) {
+ userSettings.putIntForUser(name, value, userRepository.getSelectedUserInfo().id)
+ }
+ }
+
+ suspend fun getInt(name: String, defaultValue: Int): Int {
+ return withContext(bgContext) {
+ userSettings.getIntForUser(name, defaultValue, userRepository.getSelectedUserInfo().id)
+ }
+ }
+
+ suspend fun getString(name: String): String? {
+ return withContext(bgContext) {
+ userSettings.getStringForUser(name, userRepository.getSelectedUserInfo().id)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
index 02ce74a94de6..8b1fca551d51 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/SystemSettingsRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/settings/repository/UserAwareSystemSettingsRepository.kt
@@ -14,25 +14,30 @@
* limitations under the License.
*/
-package com.android.systemui.settings
+package com.android.systemui.util.settings.repository
-import android.content.ContentResolver
+import android.provider.Settings
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.settings.data.repository.SystemSettingsRepository
-import com.android.systemui.shared.settings.data.repository.SystemSettingsRepositoryImpl
-import dagger.Module
-import dagger.Provides
+import com.android.systemui.user.data.repository.UserRepository
+import com.android.systemui.util.settings.SystemSettings
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineDispatcher
-@Module
-object SystemSettingsRepositoryModule {
- @JvmStatic
- @Provides
- @SysUISingleton
- fun provideSystemSettingsRepository(
- contentResolver: ContentResolver,
- @Background backgroundDispatcher: CoroutineDispatcher,
- ): SystemSettingsRepository =
- SystemSettingsRepositoryImpl(contentResolver, backgroundDispatcher)
-}
+/**
+ * Repository for observing values of [Settings.Secure] for the currently active user. That means
+ * when user is switched and the new user has different value, flow will emit new value.
+ */
+@SysUISingleton
+class UserAwareSystemSettingsRepository
+@Inject
+constructor(
+ systemSettings: SystemSettings,
+ userRepository: UserRepository,
+ @Background backgroundDispatcher: CoroutineDispatcher,
+ @Background bgContext: CoroutineContext,
+) :
+ UserAwareSettingsRepository(systemSettings, userRepository, backgroundDispatcher, bgContext),
+ SystemSettingsRepository
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 617aaa71d2d3..d5b8597e36ed 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -53,7 +53,9 @@ interface AudioModule {
fun provideAudioManagerIntentsReceiver(
@Application context: Context,
@Application coroutineScope: CoroutineScope,
- ): AudioManagerEventsReceiver = AudioManagerEventsReceiverImpl(context, coroutineScope)
+ @Background coroutineContext: CoroutineContext,
+ ): AudioManagerEventsReceiver =
+ AudioManagerEventsReceiverImpl(context, coroutineScope, coroutineContext)
@Provides
@SysUISingleton
@@ -82,7 +84,7 @@ interface AudioModule {
localBluetoothManager: LocalBluetoothManager?,
@Application coroutineScope: CoroutineScope,
@Background coroutineContext: CoroutineContext,
- volumeLogger: VolumeLogger
+ volumeLogger: VolumeLogger,
): AudioSharingRepository =
if (Flags.enableLeAudioSharing() && localBluetoothManager != null) {
AudioSharingRepositoryImpl(
@@ -90,7 +92,7 @@ interface AudioModule {
localBluetoothManager,
coroutineScope,
coroutineContext,
- volumeLogger
+ volumeLogger,
)
} else {
AudioSharingRepositoryEmptyImpl()
@@ -111,8 +113,7 @@ interface AudioModule {
@Provides
@SysUISingleton
- fun provideAudioSystemRepository(
- @Application context: Context,
- ): AudioSystemRepository = AudioSystemRepositoryImpl(context)
+ fun provideAudioSystemRepository(@Application context: Context): AudioSystemRepository =
+ AudioSystemRepositoryImpl(context)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
index f7ad3205f3dd..9440a9364b62 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/dagger/VolumeDialogComponent.kt
@@ -18,6 +18,7 @@ package com.android.systemui.volume.dialog.dagger
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import dagger.BindsInstance
import dagger.Subcomponent
import kotlinx.coroutines.CoroutineScope
@@ -40,6 +41,8 @@ interface VolumeDialogComponent {
@VolumeDialogScope fun volumeDialog(): com.android.systemui.volume.dialog.VolumeDialog
+ fun sliderComponentFactory(): VolumeDialogSliderComponent.Factory
+
@Subcomponent.Factory
interface Factory {
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
new file mode 100644
index 000000000000..538ee47915a8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderComponent.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
+import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
+import dagger.BindsInstance
+import dagger.Subcomponent
+
+/**
+ * This component hosts all the stuff, that Volume Dialog sliders need. It's recreated alongside
+ * each slider view.
+ */
+@VolumeDialogSliderScope
+@Subcomponent
+interface VolumeDialogSliderComponent {
+
+ fun sliderViewBinder(): VolumeDialogSliderViewBinder
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(@BindsInstance sliderType: VolumeDialogSliderType): VolumeDialogSliderComponent
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
new file mode 100644
index 000000000000..9f5e0f6ba1aa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/dagger/VolumeDialogSliderScope.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.dagger
+
+import javax.inject.Scope
+
+/**
+ * Volume Panel Slider dependency injection scope. This scope is created for each of the volume
+ * sliders in the dialog.
+ */
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+@Scope
+annotation class VolumeDialogSliderScope
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 876bf2c4a154..2967fe8ca906 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
@@ -17,22 +17,21 @@
package com.android.systemui.volume.dialog.sliders.domain.interactor
import com.android.systemui.plugins.VolumeDialogController
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapNotNull
/** Operates a state of particular slider of the Volume Dialog. */
+@VolumeDialogSliderScope
class VolumeDialogSliderInteractor
-@AssistedInject
+@Inject
constructor(
- @Assisted private val sliderType: VolumeDialogSliderType,
+ private val sliderType: VolumeDialogSliderType,
volumeDialogStateInteractor: VolumeDialogStateInteractor,
private val volumeDialogController: VolumeDialogController,
) {
@@ -56,11 +55,4 @@ constructor(
setActiveStream(sliderType.audioStream)
}
}
-
- @VolumeDialogScope
- @AssistedFactory
- interface Factory {
-
- fun create(sliderType: VolumeDialogSliderType): VolumeDialogSliderInteractor
- }
}
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 3bf8c54cb9d8..1c231b521bae 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
@@ -24,16 +24,14 @@ import com.android.systemui.lifecycle.WindowLifecycleState
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.lifecycle.viewModel
import com.android.systemui.res.R
-import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSliderViewModel
import com.android.systemui.volume.dialog.ui.utils.JankListenerFactory
import com.android.systemui.volume.dialog.ui.utils.awaitAnimation
import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider
-import dagger.assisted.Assisted
-import dagger.assisted.AssistedFactory
-import dagger.assisted.AssistedInject
+import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.launchIn
@@ -41,10 +39,11 @@ import kotlinx.coroutines.flow.onEach
private const val PROGRESS_CHANGE_ANIMATION_DURATION_MS = 80L
+@VolumeDialogSliderScope
class VolumeDialogSliderViewBinder
-@AssistedInject
+@Inject
constructor(
- @Assisted private val viewModelProvider: () -> VolumeDialogSliderViewModel,
+ private val viewModelFactory: VolumeDialogSliderViewModel.Factory,
private val jankListenerFactory: JankListenerFactory,
) {
@@ -58,7 +57,7 @@ constructor(
viewModel(
traceName = "VolumeDialogSliderViewBinder",
minWindowLifecycleState = WindowLifecycleState.ATTACHED,
- factory = { viewModelProvider() },
+ factory = { viewModelFactory.create() },
) { viewModel ->
sliderView.addOnChangeListener { _, value, fromUser ->
viewModel.setStreamVolume(value.roundToInt(), fromUser)
@@ -85,15 +84,6 @@ constructor(
)
}
}
-
- @AssistedFactory
- @VolumeDialogScope
- interface Factory {
-
- fun create(
- viewModelProvider: () -> VolumeDialogSliderViewModel
- ): VolumeDialogSliderViewBinder
- }
}
private suspend fun Slider.setValueAnimated(
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 0a4e3f481e88..a17c1e541b5e 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
@@ -28,7 +28,6 @@ import com.android.systemui.res.R
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialogScope
import com.android.systemui.volume.dialog.sliders.ui.viewmodel.VolumeDialogSlidersViewModel
import javax.inject.Inject
-import kotlin.math.abs
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -50,15 +49,17 @@ constructor(private val viewModelFactory: VolumeDialogSlidersViewModel.Factory)
) { viewModel ->
viewModel.sliders
.onEach { uiModel ->
- uiModel.sliderViewBinder.bind(volumeDialog)
+ uiModel.sliderComponent.sliderViewBinder().bind(volumeDialog)
- val floatingSliderViewBinders = uiModel.floatingSliderViewBinders
+ val floatingSliderViewBinders = uiModel.floatingSliderComponent
floatingSlidersContainer.ensureChildCount(
viewLayoutId = R.layout.volume_dialog_slider_floating,
count = floatingSliderViewBinders.size,
)
- floatingSliderViewBinders.fastForEachIndexed { index, viewBinder ->
- viewBinder.bind(floatingSlidersContainer.getChildAt(index))
+ floatingSliderViewBinders.fastForEachIndexed { index, sliderComponent ->
+ sliderComponent
+ .sliderViewBinder()
+ .bind(floatingSlidersContainer.getChildAt(index))
}
}
.launchIn(this)
@@ -76,7 +77,7 @@ private fun ViewGroup.ensureChildCount(@LayoutRes viewLayoutId: Int, count: Int)
}
childCountDelta < 0 -> {
val inflater = LayoutInflater.from(context)
- repeat(abs(childCountDelta)) { inflater.inflate(viewLayoutId, this, true) }
+ repeat(-childCountDelta) { inflater.inflate(viewLayoutId, this, true) }
}
}
}
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 ea0b49d5294e..cf04d45d54ff 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
@@ -22,7 +22,6 @@ import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
import com.android.systemui.volume.dialog.shared.model.VolumeDialogStreamModel
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
-import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@ private const val VOLUME_UPDATE_GRACE_PERIOD = 1000
class VolumeDialogSliderViewModel
@AssistedInject
constructor(
- @Assisted private val interactor: VolumeDialogSliderInteractor,
+ private val interactor: VolumeDialogSliderInteractor,
private val visibilityInteractor: VolumeDialogVisibilityInteractor,
@VolumeDialog private val coroutineScope: CoroutineScope,
private val systemClock: SystemClock,
@@ -90,11 +89,11 @@ constructor(
private fun getTimestampMillis(): Long = systemClock.uptimeMillis()
+ private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
+
@AssistedFactory
interface Factory {
- fun create(interactor: VolumeDialogSliderInteractor): VolumeDialogSliderViewModel
+ fun create(): VolumeDialogSliderViewModel
}
-
- private data class VolumeUpdate(val newVolumeLevel: Int, val timestampMillis: Long)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
index b5b292fa4a66..d1972231d373 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/sliders/ui/viewmodel/VolumeDialogSlidersViewModel.kt
@@ -17,16 +17,13 @@
package com.android.systemui.volume.dialog.sliders.ui.viewmodel
import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
+import com.android.systemui.volume.dialog.sliders.dagger.VolumeDialogSliderComponent
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
-import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
-import com.android.systemui.volume.dialog.sliders.ui.VolumeDialogSliderViewBinder
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -36,29 +33,21 @@ class VolumeDialogSlidersViewModel
constructor(
@VolumeDialog coroutineScope: CoroutineScope,
private val slidersInteractor: VolumeDialogSlidersInteractor,
- private val sliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
- private val sliderViewModelFactory: VolumeDialogSliderViewModel.Factory,
- private val sliderViewBinderFactory: VolumeDialogSliderViewBinder.Factory,
+ private val sliderComponentFactory: VolumeDialogSliderComponent.Factory,
) {
val sliders: Flow<VolumeDialogSliderUiModel> =
slidersInteractor.sliders
- .distinctUntilChanged()
.map { slidersModel ->
VolumeDialogSliderUiModel(
- sliderViewBinder = createSliderViewBinder(slidersModel.slider),
- floatingSliderViewBinders =
- slidersModel.floatingSliders.map(::createSliderViewBinder),
+ sliderComponent = sliderComponentFactory.create(slidersModel.slider),
+ floatingSliderComponent =
+ slidersModel.floatingSliders.map(sliderComponentFactory::create),
)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, null)
.filterNotNull()
- private fun createSliderViewBinder(type: VolumeDialogSliderType): VolumeDialogSliderViewBinder =
- sliderViewBinderFactory.create {
- sliderViewModelFactory.create(sliderInteractorFactory.create(type))
- }
-
@AssistedFactory
interface Factory {
@@ -68,6 +57,6 @@ constructor(
/** Models slider ui */
data class VolumeDialogSliderUiModel(
- val sliderViewBinder: VolumeDialogSliderViewBinder,
- val floatingSliderViewBinders: List<VolumeDialogSliderViewBinder>,
+ val sliderComponent: VolumeDialogSliderComponent,
+ val floatingSliderComponent: List<VolumeDialogSliderComponent>,
)
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 9be669f3df0c..869a6a2e87d5 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
@@ -18,16 +18,19 @@ package com.android.systemui.volume.dialog.ui.viewmodel
import android.content.Context
import com.android.systemui.res.R
+import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogStateInteractor
import com.android.systemui.volume.dialog.domain.interactor.VolumeDialogVisibilityInteractor
+import com.android.systemui.volume.dialog.shared.model.VolumeDialogStateModel
import com.android.systemui.volume.dialog.shared.model.VolumeDialogVisibilityModel
import com.android.systemui.volume.dialog.shared.model.streamLabel
-import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSliderInteractor
import com.android.systemui.volume.dialog.sliders.domain.interactor.VolumeDialogSlidersInteractor
+import com.android.systemui.volume.dialog.sliders.domain.model.VolumeDialogSliderType
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
/** Provides a state for the Volume Dialog. */
@@ -38,18 +41,21 @@ constructor(
private val context: Context,
dialogVisibilityInteractor: VolumeDialogVisibilityInteractor,
volumeDialogSlidersInteractor: VolumeDialogSlidersInteractor,
- private val volumeDialogSliderInteractorFactory: VolumeDialogSliderInteractor.Factory,
+ volumeDialogStateInteractor: VolumeDialogStateInteractor,
) {
val dialogVisibilityModel: Flow<VolumeDialogVisibilityModel> =
dialogVisibilityInteractor.dialogVisibility
val dialogTitle: Flow<String> =
- volumeDialogSlidersInteractor.sliders.flatMapLatest { slidersModel ->
- val interactor = volumeDialogSliderInteractorFactory.create(slidersModel.slider)
- interactor.slider.map { sliderModel ->
- context.getString(R.string.volume_dialog_title, sliderModel.streamLabel(context))
+ combine(
+ volumeDialogStateInteractor.volumeDialogState,
+ volumeDialogSlidersInteractor.sliders.map { it.slider },
+ ) { state: VolumeDialogStateModel, sliderType: VolumeDialogSliderType ->
+ state.streamModels[sliderType.audioStream]?.let { model ->
+ context.getString(R.string.volume_dialog_title, model.streamLabel(context))
+ }
}
- }
+ .filterNotNull()
@AssistedFactory
interface Factory {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
index dacd6c78b034..b9f47d7ad110 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractor.kt
@@ -22,15 +22,17 @@ import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Bundle
import android.os.Handler
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaControllerChangeModel
import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.ProducerScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
-import com.android.app.tracing.coroutines.launchTraced as launch
+import kotlinx.coroutines.flow.flowOn
interface MediaControllerInteractor {
@@ -43,14 +45,16 @@ class MediaControllerInteractorImpl
@Inject
constructor(
@Background private val backgroundHandler: Handler,
+ @Background private val backgroundCoroutineContext: CoroutineContext,
) : MediaControllerInteractor {
override fun stateChanges(mediaController: MediaController): Flow<MediaControllerChangeModel> {
return conflatedCallbackFlow {
- val callback = MediaControllerCallbackProducer(this)
- mediaController.registerCallback(callback, backgroundHandler)
- awaitClose { mediaController.unregisterCallback(callback) }
- }
+ val callback = MediaControllerCallbackProducer(this)
+ mediaController.registerCallback(callback, backgroundHandler)
+ awaitClose { mediaController.unregisterCallback(callback) }
+ }
+ .flowOn(backgroundCoroutineContext)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index aa07cfd26bdb..b3848a6d7817 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -20,10 +20,12 @@ import android.content.pm.PackageManager
import android.media.VolumeProvider
import android.media.session.MediaController
import android.util.Log
+import androidx.annotation.WorkerThread
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.volume.data.repository.LocalMediaRepository
import com.android.settingslib.volume.data.repository.MediaControllerRepository
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.Execution
import com.android.systemui.volume.panel.component.mediaoutput.data.repository.LocalMediaRepositoryFactory
import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaDeviceSessions
import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
@@ -62,6 +64,7 @@ constructor(
@Background private val backgroundCoroutineContext: CoroutineContext,
mediaControllerRepository: MediaControllerRepository,
private val mediaControllerInteractor: MediaControllerInteractor,
+ private val execution: Execution,
) {
private val activeMediaControllers: Flow<MediaControllers> =
@@ -82,9 +85,10 @@ constructor(
.map {
MediaDeviceSessions(
local = it.local?.mediaDeviceSession(),
- remote = it.remote?.mediaDeviceSession()
+ remote = it.remote?.mediaDeviceSession(),
)
}
+ .flowOn(backgroundCoroutineContext)
.stateIn(coroutineScope, SharingStarted.Eagerly, MediaDeviceSessions(null, null))
/** Returns the default [MediaDeviceSession] from [activeMediaDeviceSessions] */
@@ -115,55 +119,43 @@ constructor(
val currentConnectedDevice: Flow<MediaDevice?> =
localMediaRepository.flatMapLatest { it.currentConnectedDevice }.distinctUntilChanged()
- private suspend fun getApplicationLabel(packageName: String): CharSequence? {
- return try {
- withContext(backgroundCoroutineContext) {
- val appInfo =
- packageManager.getApplicationInfo(
- packageName,
- PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER
- )
- appInfo.loadLabel(packageManager)
- }
- } catch (e: PackageManager.NameNotFoundException) {
- Log.e(TAG, "Unable to find info for package: $packageName")
- null
- }
- }
-
/** Finds local and remote media controllers. */
- private fun getMediaControllers(
- controllers: Collection<MediaController>,
- ): MediaControllers {
- var localController: MediaController? = null
- var remoteController: MediaController? = null
- val remoteMediaSessions: MutableSet<String> = mutableSetOf()
- for (controller in controllers) {
- val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
- when (playbackInfo.playbackType) {
- MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
- // MediaController can't be local if there is a remote one for the same package
- if (localController?.packageName.equals(controller.packageName)) {
- localController = null
+ private suspend fun getMediaControllers(
+ controllers: Collection<MediaController>
+ ): MediaControllers =
+ withContext(backgroundCoroutineContext) {
+ var localController: MediaController? = null
+ var remoteController: MediaController? = null
+ val remoteMediaSessions: MutableSet<String> = mutableSetOf()
+ for (controller in controllers) {
+ val playbackInfo: MediaController.PlaybackInfo = controller.playbackInfo ?: continue
+ when (playbackInfo.playbackType) {
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_REMOTE -> {
+ // MediaController can't be local if there is a remote one for the same
+ // package
+ if (localController?.packageName.equals(controller.packageName)) {
+ localController = null
+ }
+ if (!remoteMediaSessions.contains(controller.packageName)) {
+ remoteMediaSessions.add(controller.packageName)
+ remoteController = chooseController(remoteController, controller)
+ }
}
- if (!remoteMediaSessions.contains(controller.packageName)) {
- remoteMediaSessions.add(controller.packageName)
- remoteController = chooseController(remoteController, controller)
+ MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
+ if (controller.packageName in remoteMediaSessions) continue
+ localController = chooseController(localController, controller)
}
}
- MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL -> {
- if (controller.packageName in remoteMediaSessions) continue
- localController = chooseController(localController, controller)
- }
}
+ MediaControllers(local = localController, remote = remoteController)
}
- return MediaControllers(local = localController, remote = remoteController)
- }
+ @WorkerThread
private fun chooseController(
currentController: MediaController?,
newController: MediaController,
): MediaController {
+ require(!execution.isMainThread())
if (currentController == null) {
return newController
}
@@ -175,12 +167,26 @@ constructor(
return currentController
}
- private suspend fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+ @WorkerThread
+ private fun MediaController.mediaDeviceSession(): MediaDeviceSession? {
+ require(!execution.isMainThread())
+ val applicationLabel =
+ try {
+ packageManager
+ .getApplicationInfo(
+ packageName,
+ PackageManager.MATCH_DISABLED_COMPONENTS or PackageManager.MATCH_ANY_USER,
+ )
+ .loadLabel(packageManager)
+ } catch (e: PackageManager.NameNotFoundException) {
+ Log.e(TAG, "Unable to find info for package: $packageName")
+ null
+ } ?: return null
return MediaDeviceSession(
packageName = packageName,
sessionToken = sessionToken,
canAdjustVolume = playbackInfo.volumeControl != VolumeProvider.VOLUME_CONTROL_FIXED,
- appLabel = getApplicationLabel(packageName) ?: return null
+ appLabel = applicationLabel,
)
}
@@ -195,10 +201,7 @@ constructor(
.onStart { emit(this@stateChanges) }
}
- private data class MediaControllers(
- val local: MediaController?,
- val remote: MediaController?,
- )
+ private data class MediaControllers(val local: MediaController?, val remote: MediaController?)
private companion object {
const val TAG = "MediaOutputInteractor"
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
index aa8044515ea2..aa8044515ea2 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
index a840d3cb1225..7abff2c74531 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
"bottom": 0
},
{
- "left": 94,
- "top": 284,
- "right": 206,
+ "left": 104,
+ "top": 285,
+ "right": 215,
"bottom": 414
},
{
- "left": 83,
- "top": 251,
- "right": 219,
+ "left": 92,
+ "top": 252,
+ "right": 227,
"bottom": 447
},
{
- "left": 70,
- "top": 212,
- "right": 234,
- "bottom": 485
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
},
{
- "left": 57,
- "top": 173,
- "right": 250,
- "bottom": 522
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
},
{
- "left": 46,
- "top": 139,
- "right": 264,
- "bottom": 555
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
},
{
- "left": 36,
- "top": 109,
- "right": 276,
- "bottom": 584
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
},
{
- "left": 28,
- "top": 84,
- "right": 285,
- "bottom": 608
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
},
{
- "left": 21,
- "top": 65,
- "right": 293,
- "bottom": 627
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
},
{
- "left": 16,
- "top": 49,
- "right": 300,
- "bottom": 642
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
},
{
- "left": 12,
- "top": 36,
- "right": 305,
- "bottom": 653
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
},
{
- "left": 9,
- "top": 27,
- "right": 308,
- "bottom": 662
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
},
{
"left": 7,
- "top": 20,
+ "top": 24,
"right": 312,
- "bottom": 669
+ "bottom": 673
},
{
"left": 5,
- "top": 14,
+ "top": 18,
"right": 314,
- "bottom": 675
+ "bottom": 678
},
{
"left": 4,
- "top": 11,
+ "top": 13,
"right": 315,
- "bottom": 678
+ "bottom": 681
},
{
"left": 3,
- "top": 8,
+ "top": 10,
"right": 316,
- "bottom": 681
+ "bottom": 684
},
{
"left": 2,
- "top": 5,
+ "top": 7,
"right": 317,
- "bottom": 684
+ "bottom": 685
},
{
"left": 1,
- "top": 4,
+ "top": 5,
"right": 318,
- "bottom": 685
+ "bottom": 687
},
{
"left": 1,
- "top": 3,
+ "top": 4,
"right": 318,
- "bottom": 686
+ "bottom": 688
},
{
"left": 0,
- "top": 2,
+ "top": 3,
"right": 319,
- "bottom": 687
+ "bottom": 688
}
]
},
@@ -371,5 +371,14 @@
0
]
}
- ]
+ ],
+ "\/\/metadata": {
+ "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenLaunching_withSpring.json",
+ "goldenIdentifier": "backgroundAnimation_whenLaunching_withSpring",
+ "testClassName": "TransitionAnimatorTest",
+ "testMethodName": "backgroundAnimation_whenLaunching[true]",
+ "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+ "result": "FAILED",
+ "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenLaunching_withSpring.actual.mp4"
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
index aa8044515ea2..aa8044515ea2 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenReturning.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
index a840d3cb1225..561961145ca1 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimation_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
"bottom": 0
},
{
- "left": 94,
- "top": 284,
- "right": 206,
+ "left": 104,
+ "top": 285,
+ "right": 215,
"bottom": 414
},
{
- "left": 83,
- "top": 251,
- "right": 219,
+ "left": 92,
+ "top": 252,
+ "right": 227,
"bottom": 447
},
{
- "left": 70,
- "top": 212,
- "right": 234,
- "bottom": 485
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
},
{
- "left": 57,
- "top": 173,
- "right": 250,
- "bottom": 522
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
},
{
- "left": 46,
- "top": 139,
- "right": 264,
- "bottom": 555
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
},
{
- "left": 36,
- "top": 109,
- "right": 276,
- "bottom": 584
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
},
{
- "left": 28,
- "top": 84,
- "right": 285,
- "bottom": 608
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
},
{
- "left": 21,
- "top": 65,
- "right": 293,
- "bottom": 627
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
},
{
- "left": 16,
- "top": 49,
- "right": 300,
- "bottom": 642
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
},
{
- "left": 12,
- "top": 36,
- "right": 305,
- "bottom": 653
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
},
{
- "left": 9,
- "top": 27,
- "right": 308,
- "bottom": 662
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
},
{
"left": 7,
- "top": 20,
+ "top": 24,
"right": 312,
- "bottom": 669
+ "bottom": 673
},
{
"left": 5,
- "top": 14,
+ "top": 18,
"right": 314,
- "bottom": 675
+ "bottom": 678
},
{
"left": 4,
- "top": 11,
+ "top": 13,
"right": 315,
- "bottom": 678
+ "bottom": 681
},
{
"left": 3,
- "top": 8,
+ "top": 10,
"right": 316,
- "bottom": 681
+ "bottom": 684
},
{
"left": 2,
- "top": 5,
+ "top": 7,
"right": 317,
- "bottom": 684
+ "bottom": 685
},
{
"left": 1,
- "top": 4,
+ "top": 5,
"right": 318,
- "bottom": 685
+ "bottom": 687
},
{
"left": 1,
- "top": 3,
+ "top": 4,
"right": 318,
- "bottom": 686
+ "bottom": 688
},
{
"left": 0,
- "top": 2,
+ "top": 3,
"right": 319,
- "bottom": 687
+ "bottom": 688
}
]
},
@@ -371,5 +371,14 @@
0
]
}
- ]
+ ],
+ "\/\/metadata": {
+ "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimation_whenReturning_withSpring.json",
+ "goldenIdentifier": "backgroundAnimation_whenReturning_withSpring",
+ "testClassName": "TransitionAnimatorTest",
+ "testMethodName": "backgroundAnimation_whenReturning[true]",
+ "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+ "result": "FAILED",
+ "videoLocation": "TransitionAnimatorTest\/backgroundAnimation_whenReturning_withSpring.actual.mp4"
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
index 7f623575fef4..7f623575fef4 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
index 18eedd450751..825190ba7a32 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenLaunching_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenLaunching_withSpring.json
@@ -33,118 +33,118 @@
"bottom": 0
},
{
- "left": 94,
- "top": 284,
- "right": 206,
+ "left": 104,
+ "top": 285,
+ "right": 215,
"bottom": 414
},
{
- "left": 83,
- "top": 251,
- "right": 219,
+ "left": 92,
+ "top": 252,
+ "right": 227,
"bottom": 447
},
{
- "left": 70,
- "top": 212,
- "right": 234,
- "bottom": 485
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
},
{
- "left": 57,
- "top": 173,
- "right": 250,
- "bottom": 522
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
},
{
- "left": 46,
- "top": 139,
- "right": 264,
- "bottom": 555
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
},
{
- "left": 36,
- "top": 109,
- "right": 276,
- "bottom": 584
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
},
{
- "left": 28,
- "top": 84,
- "right": 285,
- "bottom": 608
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
},
{
- "left": 21,
- "top": 65,
- "right": 293,
- "bottom": 627
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
},
{
- "left": 16,
- "top": 49,
- "right": 300,
- "bottom": 642
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
},
{
- "left": 12,
- "top": 36,
- "right": 305,
- "bottom": 653
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
},
{
- "left": 9,
- "top": 27,
- "right": 308,
- "bottom": 662
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
},
{
"left": 7,
- "top": 20,
+ "top": 24,
"right": 312,
- "bottom": 669
+ "bottom": 673
},
{
"left": 5,
- "top": 14,
+ "top": 18,
"right": 314,
- "bottom": 675
+ "bottom": 678
},
{
"left": 4,
- "top": 11,
+ "top": 13,
"right": 315,
- "bottom": 678
+ "bottom": 681
},
{
"left": 3,
- "top": 8,
+ "top": 10,
"right": 316,
- "bottom": 681
+ "bottom": 684
},
{
"left": 2,
- "top": 5,
+ "top": 7,
"right": 317,
- "bottom": 684
+ "bottom": 685
},
{
"left": 1,
- "top": 4,
+ "top": 5,
"right": 318,
- "bottom": 685
+ "bottom": 687
},
{
"left": 1,
- "top": 3,
+ "top": 4,
"right": 318,
- "bottom": 686
+ "bottom": 688
},
{
"left": 0,
- "top": 2,
+ "top": 3,
"right": 319,
- "bottom": 687
+ "bottom": 688
}
]
},
@@ -371,5 +371,14 @@
0
]
}
- ]
+ ],
+ "\/\/metadata": {
+ "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenLaunching_withSpring.json",
+ "goldenIdentifier": "backgroundAnimationWithoutFade_whenLaunching_withSpring",
+ "testClassName": "TransitionAnimatorTest",
+ "testMethodName": "backgroundAnimationWithoutFade_whenLaunching[true]",
+ "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+ "result": "FAILED",
+ "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenLaunching_withSpring.actual.mp4"
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
index 98005c53f6e0..98005c53f6e0 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withAnimator.json
diff --git a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
index 18eedd450751..63c263175122 100644
--- a/packages/SystemUI/tests/goldens/backgroundAnimationWithoutFade_whenReturning_withSpring.json
+++ b/packages/SystemUI/tests/goldens/backgroundAnimationTimeSeries_withoutFade_whenReturning_withSpring.json
@@ -33,118 +33,118 @@
"bottom": 0
},
{
- "left": 94,
- "top": 284,
- "right": 206,
+ "left": 104,
+ "top": 285,
+ "right": 215,
"bottom": 414
},
{
- "left": 83,
- "top": 251,
- "right": 219,
+ "left": 92,
+ "top": 252,
+ "right": 227,
"bottom": 447
},
{
- "left": 70,
- "top": 212,
- "right": 234,
- "bottom": 485
+ "left": 77,
+ "top": 213,
+ "right": 242,
+ "bottom": 486
},
{
- "left": 57,
- "top": 173,
- "right": 250,
- "bottom": 522
+ "left": 63,
+ "top": 175,
+ "right": 256,
+ "bottom": 524
},
{
- "left": 46,
- "top": 139,
- "right": 264,
- "bottom": 555
+ "left": 50,
+ "top": 141,
+ "right": 269,
+ "bottom": 558
},
{
- "left": 36,
- "top": 109,
- "right": 276,
- "bottom": 584
+ "left": 40,
+ "top": 112,
+ "right": 279,
+ "bottom": 587
},
{
- "left": 28,
- "top": 84,
- "right": 285,
- "bottom": 608
+ "left": 31,
+ "top": 88,
+ "right": 288,
+ "bottom": 611
},
{
- "left": 21,
- "top": 65,
- "right": 293,
- "bottom": 627
+ "left": 23,
+ "top": 68,
+ "right": 296,
+ "bottom": 631
},
{
- "left": 16,
- "top": 49,
- "right": 300,
- "bottom": 642
+ "left": 18,
+ "top": 53,
+ "right": 301,
+ "bottom": 646
},
{
- "left": 12,
- "top": 36,
- "right": 305,
- "bottom": 653
+ "left": 13,
+ "top": 41,
+ "right": 306,
+ "bottom": 658
},
{
- "left": 9,
- "top": 27,
- "right": 308,
- "bottom": 662
+ "left": 10,
+ "top": 31,
+ "right": 309,
+ "bottom": 667
},
{
"left": 7,
- "top": 20,
+ "top": 24,
"right": 312,
- "bottom": 669
+ "bottom": 673
},
{
"left": 5,
- "top": 14,
+ "top": 18,
"right": 314,
- "bottom": 675
+ "bottom": 678
},
{
"left": 4,
- "top": 11,
+ "top": 13,
"right": 315,
- "bottom": 678
+ "bottom": 681
},
{
"left": 3,
- "top": 8,
+ "top": 10,
"right": 316,
- "bottom": 681
+ "bottom": 684
},
{
"left": 2,
- "top": 5,
+ "top": 7,
"right": 317,
- "bottom": 684
+ "bottom": 685
},
{
"left": 1,
- "top": 4,
+ "top": 5,
"right": 318,
- "bottom": 685
+ "bottom": 687
},
{
"left": 1,
- "top": 3,
+ "top": 4,
"right": 318,
- "bottom": 686
+ "bottom": 688
},
{
"left": 0,
- "top": 2,
+ "top": 3,
"right": 319,
- "bottom": 687
+ "bottom": 688
}
]
},
@@ -371,5 +371,14 @@
0
]
}
- ]
+ ],
+ "\/\/metadata": {
+ "goldenRepoPath": "frameworks\/base\/packages\/SystemUI\/tests\/goldens\/backgroundAnimationWithoutFade_whenReturning_withSpring.json",
+ "goldenIdentifier": "backgroundAnimationWithoutFade_whenReturning_withSpring",
+ "testClassName": "TransitionAnimatorTest",
+ "testMethodName": "backgroundAnimationWithoutFade_whenReturning[true]",
+ "deviceLocalPath": "\/data\/user\/0\/com.android.systemui.tests\/files\/platform_screenshots",
+ "result": "FAILED",
+ "videoLocation": "TransitionAnimatorTest\/backgroundAnimationWithoutFade_whenReturning_withSpring.actual.mp4"
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
index a95735e56f04..82cfab6fde06 100644
--- a/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
+++ b/packages/SystemUI/tests/src/com/android/AAAPlusPlusVerifySysuiRequiredTestPropertiesTest.java
@@ -56,7 +56,6 @@ import java.util.Collections;
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestCase {
-
private static final String TAG = "AAA++VerifyTest";
private static final Class[] BASE_CLS_TO_INCLUDE = {
@@ -149,6 +148,9 @@ public class AAAPlusPlusVerifySysuiRequiredTestPropertiesTest extends SysuiTestC
*/
private boolean isTestClass(Class<?> loadedClass) {
try {
+ if (loadedClass.getAnnotation(SkipSysuiVerification.class) != null) {
+ return false;
+ }
if (Modifier.isAbstract(loadedClass.getModifiers())) {
logDebug(String.format("Skipping abstract class %s: not a test",
loadedClass.getName()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
index 288ed4dc9d4f..a1f59c2cc2b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt
@@ -20,18 +20,20 @@ import android.animation.AnimatorRuleRecordingSpec
import android.animation.AnimatorTestRuleToolkit
import android.animation.MotionControl
import android.animation.recordMotion
+import android.graphics.Color
+import android.graphics.PointF
import android.graphics.drawable.GradientDrawable
import android.platform.test.annotations.MotionTest
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.systemui.SysuiTestCase
import com.android.systemui.activity.EmptyTestActivity
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
+import com.android.systemui.runOnMainThreadAndWaitForIdleSync
import kotlin.test.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -47,13 +49,25 @@ import platform.test.screenshot.PathConfig
@SmallTest
@MotionTest
@RunWith(ParameterizedAndroidJunit4::class)
-class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
+class TransitionAnimatorTest(
+ private val fadeWindowBackgroundLayer: Boolean,
+ private val isLaunching: Boolean,
+ private val useSpring: Boolean,
+) : SysuiTestCase() {
companion object {
private const val GOLDENS_PATH = "frameworks/base/packages/SystemUI/tests/goldens"
- @get:Parameters(name = "{0}")
+ @get:Parameters(name = "fadeBackground={0}, isLaunching={1}, useSpring={2}")
@JvmStatic
- val useSpringValues = booleanArrayOf(false, true).toList()
+ val parameterValues = buildList {
+ booleanArrayOf(true, false).forEach { fadeBackground ->
+ booleanArrayOf(true, false).forEach { isLaunching ->
+ booleanArrayOf(true, false).forEach { useSpring ->
+ add(arrayOf(fadeBackground, isLaunching, useSpring))
+ }
+ }
+ }
+ }
}
private val kosmos = Kosmos()
@@ -66,11 +80,23 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
ActivityTransitionAnimator.SPRING_TIMINGS,
ActivityTransitionAnimator.SPRING_INTERPOLATORS,
)
- private val withSpring =
+ private val fade =
+ if (fadeWindowBackgroundLayer) {
+ "withFade"
+ } else {
+ "withoutFade"
+ }
+ private val direction =
+ if (isLaunching) {
+ "whenLaunching"
+ } else {
+ "whenReturning"
+ }
+ private val mode =
if (useSpring) {
- "_withSpring"
+ "withSpring"
} else {
- ""
+ "withAnimator"
}
@get:Rule(order = 1) val activityRule = ActivityScenarioRule(EmptyTestActivity::class.java)
@@ -83,113 +109,75 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
)
@Test
- fun backgroundAnimation_whenLaunching() {
- val backgroundLayer = GradientDrawable().apply { alpha = 0 }
- val animator =
- setUpTest(backgroundLayer, isLaunching = true).apply {
- getInstrumentation().runOnMainSync { start() }
- }
+ fun backgroundAnimationTimeSeries() {
+ val transitionContainer = createScene()
+ val backgroundLayer = createBackgroundLayer()
+ val animation = createAnimation(transitionContainer, backgroundLayer)
- val recordedMotion = recordMotion(backgroundLayer, animator)
+ val recordedMotion = record(backgroundLayer, animation)
motionRule
.assertThat(recordedMotion)
- .timeSeriesMatchesGolden("backgroundAnimation_whenLaunching$withSpring")
+ .timeSeriesMatchesGolden("backgroundAnimationTimeSeries_${fade}_${direction}_$mode")
}
- @Test
- fun backgroundAnimation_whenReturning() {
- val backgroundLayer = GradientDrawable().apply { alpha = 0 }
- val animator =
- setUpTest(backgroundLayer, isLaunching = false).apply {
- getInstrumentation().runOnMainSync { start() }
- }
-
- val recordedMotion = recordMotion(backgroundLayer, animator)
-
- motionRule
- .assertThat(recordedMotion)
- .timeSeriesMatchesGolden("backgroundAnimation_whenReturning$withSpring")
- }
-
- @Test
- fun backgroundAnimationWithoutFade_whenLaunching() {
- val backgroundLayer = GradientDrawable().apply { alpha = 0 }
- val animator =
- setUpTest(backgroundLayer, isLaunching = true, fadeWindowBackgroundLayer = false)
- .apply { getInstrumentation().runOnMainSync { start() } }
-
- val recordedMotion = recordMotion(backgroundLayer, animator)
-
- motionRule
- .assertThat(recordedMotion)
- .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenLaunching$withSpring")
- }
-
- @Test
- fun backgroundAnimationWithoutFade_whenReturning() {
- val backgroundLayer = GradientDrawable().apply { alpha = 0 }
- val animator =
- setUpTest(backgroundLayer, isLaunching = false, fadeWindowBackgroundLayer = false)
- .apply { getInstrumentation().runOnMainSync { start() } }
-
- val recordedMotion = recordMotion(backgroundLayer, animator)
-
- motionRule
- .assertThat(recordedMotion)
- .timeSeriesMatchesGolden("backgroundAnimationWithoutFade_whenReturning$withSpring")
- }
-
- private fun setUpTest(
- backgroundLayer: GradientDrawable,
- isLaunching: Boolean,
- fadeWindowBackgroundLayer: Boolean = true,
- ): TransitionAnimator.Animation {
+ private fun createScene(): ViewGroup {
lateinit var transitionContainer: ViewGroup
activityRule.scenario.onActivity { activity ->
- transitionContainer = FrameLayout(activity).apply { setBackgroundColor(0x00FF00) }
+ transitionContainer = FrameLayout(activity)
activity.setContentView(transitionContainer)
}
waitForIdleSync()
+ return transitionContainer
+ }
+ private fun createBackgroundLayer() =
+ GradientDrawable().apply {
+ setColor(Color.BLACK)
+ alpha = 0
+ }
+
+ private fun createAnimation(
+ transitionContainer: ViewGroup,
+ backgroundLayer: GradientDrawable,
+ ): TransitionAnimator.Animation {
val controller = TestController(transitionContainer, isLaunching)
- return transitionAnimator.createAnimation(
- controller,
- controller.createAnimatorState(),
- createEndState(transitionContainer),
- backgroundLayer,
- fadeWindowBackgroundLayer,
- useSpring = useSpring,
- )
- }
- private fun createEndState(container: ViewGroup): TransitionAnimator.State {
val containerLocation = IntArray(2)
- container.getLocationOnScreen(containerLocation)
- return TransitionAnimator.State(
- left = containerLocation[0],
- top = containerLocation[1],
- right = containerLocation[0] + 320,
- bottom = containerLocation[1] + 690,
- topCornerRadius = 0f,
- bottomCornerRadius = 0f,
- )
+ transitionContainer.getLocationOnScreen(containerLocation)
+ val endState =
+ TransitionAnimator.State(
+ left = containerLocation[0],
+ top = containerLocation[1],
+ right = containerLocation[0] + 320,
+ bottom = containerLocation[1] + 690,
+ topCornerRadius = 0f,
+ bottomCornerRadius = 0f,
+ )
+
+ val startVelocity =
+ if (useSpring) {
+ PointF(2500f, 30000f)
+ } else {
+ null
+ }
+
+ return transitionAnimator
+ .createAnimation(
+ controller,
+ controller.createAnimatorState(),
+ endState,
+ backgroundLayer,
+ fadeWindowBackgroundLayer,
+ startVelocity = startVelocity,
+ )
+ .apply { runOnMainThreadAndWaitForIdleSync { start() } }
}
- private fun recordMotion(
+ private fun record(
backgroundLayer: GradientDrawable,
animation: TransitionAnimator.Animation,
): RecordedMotion {
- fun record(motionControl: MotionControl, sampleIntervalMs: Long): RecordedMotion {
- return motionRule.recordMotion(
- AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) {
- feature(DrawableFeatureCaptures.bounds, "bounds")
- feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
- feature(DrawableFeatureCaptures.alpha, "alpha")
- }
- )
- }
-
val motionControl: MotionControl
val sampleIntervalMs: Long
if (useSpring) {
@@ -204,9 +192,13 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() {
sampleIntervalMs = 20L
}
- var recording: RecordedMotion? = null
- getInstrumentation().runOnMainSync { recording = record(motionControl, sampleIntervalMs) }
- return recording!!
+ return motionRule.recordMotion(
+ AnimatorRuleRecordingSpec(backgroundLayer, motionControl, sampleIntervalMs) {
+ feature(DrawableFeatureCaptures.bounds, "bounds")
+ feature(DrawableFeatureCaptures.cornerRadii, "corner_radii")
+ feature(DrawableFeatureCaptures.alpha, "alpha")
+ }
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e1b8a1d9971c..91f9cce5b69b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -212,6 +212,20 @@ open class AuthContainerViewTest : SysuiTestCase() {
}
@Test
+ fun testDimissOnLock() {
+ val container = initializeFingerprintContainer(addToView = true)
+ assertThat(container.parent).isNotNull()
+ val root = container.rootView
+
+ // Simulate sleep/lock invocation
+ container.onStartedGoingToSleep()
+ waitForIdleSync()
+
+ assertThat(container.parent).isNull()
+ assertThat(root.isAttachedToWindow).isFalse()
+ }
+
+ @Test
fun testCredentialPasswordDismissesOnBack() {
val container = initializeCredentialPasswordContainer(addToView = true)
assertThat(container.parent).isNotNull()
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 9ace8e981077..387cc084f9cd 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
@@ -42,6 +42,7 @@ import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.res.R
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -109,7 +110,7 @@ class BouncerContentTest : SysuiTestCase() {
@Test
fun doubleClick_swapSide() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val motion =
recordMotion(
content = { BouncerContentUnderTest() },
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 088bb02512b5..768f1dc8b78c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -29,6 +29,7 @@ import com.android.systemui.haptics.msdl.bouncerHapticPlayer
import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.takeWhile
@@ -71,7 +72,7 @@ class PatternBouncerTest : SysuiTestCase() {
@Test
fun entryAnimation() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val motion =
recordMotion(
content = { play -> if (play) PatternBouncerUnderTest() },
@@ -89,7 +90,7 @@ class PatternBouncerTest : SysuiTestCase() {
@Test
fun animateFailure() =
- motionTestRule.runTest {
+ motionTestRule.runTest(timeout = 30.seconds) {
val failureAnimationMotionControl =
MotionControl(
delayReadyToPlay = {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index 6061063db903..562481567536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -20,6 +20,7 @@ import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static com.android.systemui.Flags.FLAG_CLIPBOARD_SHARED_TRANSITIONS;
import static com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE;
+import static com.android.systemui.Flags.FLAG_SHOW_CLIPBOARD_INDICATION;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED;
@@ -121,6 +122,24 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
+ private class FakeClipboardIndicationProvider implements ClipboardIndicationProvider {
+ private ClipboardIndicationCallback mIndicationCallback;
+
+ public void notifyIndicationTextChanged(CharSequence indicationText) {
+ if (mIndicationCallback != null) {
+ mIndicationCallback.onIndicationTextChanged(indicationText);
+ }
+ }
+
+ @Override
+ public void getIndicationText(ClipboardIndicationCallback callback) {
+ mIndicationCallback = callback;
+ }
+ }
+
+ private FakeClipboardIndicationProvider mClipboardIndicationProvider =
+ new FakeClipboardIndicationProvider();
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
@@ -156,6 +175,7 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
mExecutor,
mClipboardImageLoader,
mClipboardTransitionExecutor,
+ mClipboardIndicationProvider,
mUiEventLogger);
verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -305,6 +325,17 @@ public class ClipboardOverlayControllerTest extends SysuiTestCase {
}
@Test
+ @EnableFlags(FLAG_SHOW_CLIPBOARD_INDICATION)
+ public void test_onIndicationTextChanged_setIndicationTextCorrectly() {
+ initController();
+ mOverlayController.setClipData(mSampleClipData, "");
+
+ mClipboardIndicationProvider.notifyIndicationTextChanged("copied");
+
+ verify(mClipboardOverlayView).setIndicationText("copied");
+ }
+
+ @Test
@DisableFlags(FLAG_CLIPBOARD_SHARED_TRANSITIONS)
public void test_viewCallbacks_onShareTapped_sharedTransitionsOff() {
initController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
index 7709a65712a1..0ab4cd05fea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt
@@ -21,7 +21,6 @@ import android.graphics.Insets
import android.graphics.Rect
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.view.Display.DEFAULT_DISPLAY
import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD
@@ -76,9 +75,8 @@ class PolicyRequestProcessorTest {
/** Tests applying CaptureParameters with 'IsolatedTask' CaptureType */
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
fun testProcess_newPolicy_isolatedTask() = runTest {
- val taskImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val taskImage = Bitmap.createBitmap(200, 100, Bitmap.Config.ARGB_8888)
/* Create a policy request processor with no capture policies */
val requestProcessor =
@@ -96,9 +94,15 @@ class PolicyRequestProcessorTest {
requestProcessor.modify(
screenshotRequest,
CaptureParameters(
- IsolatedTask(taskId = TASK_ID, taskBounds = null),
- ComponentName.unflattenFromString(FILES),
- UserHandle.of(WORK),
+ type = IsolatedTask(taskId = 100, taskBounds = Rect(0, 100, 200, 200)),
+ contentTask =
+ TaskReference(
+ taskId = 1001,
+ component = ComponentName.unflattenFromString(FILES)!!,
+ owner = UserHandle.CURRENT,
+ bounds = Rect(100, 100, 200, 200),
+ ),
+ owner = UserHandle.of(WORK),
),
)
@@ -112,14 +116,13 @@ class PolicyRequestProcessorTest {
.that(result.topComponent)
.isEqualTo(ComponentName.unflattenFromString(FILES))
- assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID)
+ assertWithMessage("Task ID").that(result.taskId).isEqualTo(1001)
}
/** Tests applying CaptureParameters with 'FullScreen' CaptureType */
@Test
- @EnableFlags(Flags.FLAG_SCREENSHOT_POLICY_SPLIT_AND_DESKTOP_MODE)
fun testProcess_newPolicy_fullScreen() = runTest {
- val screenImage = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
+ val screenImage = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
/* Create a policy request processor with no capture policies */
val requestProcessor =
@@ -136,7 +139,17 @@ class PolicyRequestProcessorTest {
val result =
requestProcessor.modify(
screenshotRequest,
- CaptureParameters(FullScreen(displayId = 0), defaultComponent, defaultOwner),
+ CaptureParameters(
+ type = FullScreen(displayId = 0),
+ contentTask =
+ TaskReference(
+ taskId = 1234,
+ component = defaultComponent,
+ owner = UserHandle.CURRENT,
+ bounds = Rect(1, 2, 3, 4),
+ ),
+ owner = defaultOwner,
+ ),
)
assertWithMessage("The result bitmap").that(result.bitmap).isSameInstanceAs(screenImage)
@@ -149,7 +162,11 @@ class PolicyRequestProcessorTest {
.that(result.topComponent)
.isEqualTo(defaultComponent)
- assertWithMessage("Task ID").that(result.taskId).isEqualTo(-1)
+ assertWithMessage("The bounds of the screenshot")
+ .that(result.originalScreenBounds)
+ .isEqualTo(Rect(0, 0, 100, 100))
+
+ assertWithMessage("Task ID").that(result.taskId).isEqualTo(1234)
}
/** Tests behavior when no policies are applied */
@@ -230,7 +247,7 @@ class PolicyRequestProcessorTest {
policy = "",
reason = "",
parameters =
- CaptureParameters(
+ LegacyCaptureParameters(
IsolatedTask(taskId = 0, taskBounds = null),
null,
UserHandle.CURRENT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index a8929a63a812..f870200c2b00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.logging;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -59,8 +57,6 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor;
@@ -118,11 +114,6 @@ public class NotificationLoggerTest extends SysuiTestCase {
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
- private final ActiveNotificationListRepository mActiveNotificationListRepository =
- new ActiveNotificationListRepository();
- private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(mActiveNotificationListRepository,
- StandardTestDispatcher(null, null));
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -137,7 +128,7 @@ public class NotificationLoggerTest extends SysuiTestCase {
mKeyguardRepository,
mHeadsUpManager,
mPowerInteractor,
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
() -> mKosmos.getSceneInteractor());
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 625963f5ec7a..0427011c06f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -28,8 +28,6 @@ import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanki
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
-import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -87,8 +85,6 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -159,12 +155,6 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
@Mock private UserManager mUserManager;
- private final ActiveNotificationListRepository mActiveNotificationListRepository =
- new ActiveNotificationListRepository();
- private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
- new ActiveNotificationsInteractor(mActiveNotificationListRepository,
- StandardTestDispatcher(null, null));
-
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
@Before
@@ -179,7 +169,7 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
new FakeKeyguardRepository(),
mHeadsUpManager,
PowerInteractorFactory.create().getPowerInteractor(),
- mActiveNotificationsInteractor,
+ mKosmos.getActiveNotificationsInteractor(),
() -> mKosmos.getSceneInteractor()
);
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 05ec85a4cb4f..d2350bc82667 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
@@ -19,6 +19,7 @@ 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;
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.RUBBER_BAND_FACTOR_NORMAL;
@@ -54,6 +55,7 @@ import android.graphics.Rect;
import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
import android.testing.TestableResources;
import android.util.MathUtils;
@@ -64,13 +66,13 @@ import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
import android.widget.TextView;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.ExpandHelper;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.BrokenWithSceneContainer;
import com.android.systemui.flags.DisableSceneContainer;
import com.android.systemui.flags.EnableSceneContainer;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -118,16 +120,25 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList;
+import java.util.List;
import java.util.function.Consumer;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
/**
* Tests for {@link NotificationStackScrollLayout}.
*/
@SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
@TestableLooper.RunWithLooper
public class NotificationStackScrollLayoutTest extends SysuiTestCase {
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return parameterizeSceneContainerFlag();
+ }
+
private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
private NotificationStackScrollLayout mStackScroller; // Normally test this
private NotificationStackScrollLayout mStackScrollerInternal; // See explanation below
@@ -154,6 +165,11 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
@Mock private AvalancheController mAvalancheController;
+ public NotificationStackScrollLayoutTest(FlagsParameterization flags) {
+ super();
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() throws Exception {
allowTestableLooperAsMainThread();
@@ -353,6 +369,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void updateStackEndHeightAndStackHeight_onlyUpdatesStackHeightDuringSwipeUp() {
final float expansionFraction = 0.5f;
mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -366,6 +383,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ @DisableSceneContainer
public void setPanelFlinging_updatesStackEndHeightOnlyOnFinish() {
final float expansionFraction = 0.5f;
mAmbientState.setStatusBarState(StatusBarState.KEYGUARD);
@@ -1431,6 +1449,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
@Test
@EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ @BrokenWithSceneContainer(bugId = 332732878) // because NSSL#mAnimationsEnabled is always true
public void testGenerateHeadsUpAnimation_isSeenInShade_noAnimation() {
// GIVEN NSSL is ready for HUN animations
Consumer<Boolean> headsUpAnimatingAwayListener = mock(BooleanConsumer.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index f472fd1019eb..7d019bf1be92 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -155,6 +155,7 @@ import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeControllerImpl;
import com.android.systemui.shade.ShadeExpansionStateManager;
import com.android.systemui.shade.ShadeLogger;
+import com.android.systemui.shade.StatusBarLongPressGestureDetector;
import com.android.systemui.shared.notifications.domain.interactor.NotificationSettingsInteractor;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyboardShortcutListSearch;
@@ -174,8 +175,8 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.core.StatusBarConnectedDisplays;
import com.android.systemui.statusbar.core.StatusBarInitializerImpl;
-import com.android.systemui.statusbar.core.StatusBarOrchestrator;
import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository;
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
@@ -371,7 +372,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@Mock private NotificationSettingsInteractor mNotificationSettingsInteractor;
@Mock private ViewCaptureAwareWindowManager mViewCaptureAwareWindowManager;
- @Mock private StatusBarOrchestrator mStatusBarOrchestrator;
+ @Mock private StatusBarLongPressGestureDetector mStatusBarLongPressGestureDetector;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeGlobalSettings mFakeGlobalSettings = new FakeGlobalSettings();
@@ -387,6 +388,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor =
mKosmos.getBrightnessMirrorShowingInteractor();
+
+ private final StatusBarModePerDisplayRepository mStatusBarModePerDisplayRepository =
+ mKosmos.getStatusBarModePerDisplayRepository();
private ScrimController mScrimController;
@Before
@@ -537,6 +541,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mAutoHideController,
new StatusBarInitializerImpl(
mStatusBarWindowController,
+ mStatusBarModePerDisplayRepository,
mCollapsedStatusBarFragmentProvider,
mock(StatusBarRootFactory.class),
mock(HomeStatusBarComponent.Factory.class),
@@ -602,6 +607,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mShadeController,
mWindowRootViewVisibilityInteractor,
mStatusBarKeyguardViewManager,
+ () -> mStatusBarLongPressGestureDetector,
mViewMediatorCallback,
mInitController,
new Handler(TestableLooper.get(this).getLooper()),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 638f195df00c..69efa87a9cac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -40,14 +40,13 @@ import com.android.systemui.flags.Flags
import com.android.systemui.plugins.fakeDarkIconDispatcher
import com.android.systemui.res.R
import com.android.systemui.scene.ui.view.WindowRootView
-import com.android.systemui.shade.LongPressGestureDetector
import com.android.systemui.shade.ShadeControllerImpl
import com.android.systemui.shade.ShadeLogger
import com.android.systemui.shade.ShadeViewController
+import com.android.systemui.shade.StatusBarLongPressGestureDetector
import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.fakeStatusBarContentInsetsProviderStore
-import com.android.systemui.statusbar.data.repository.statusBarContentInsetsProviderStore
import com.android.systemui.statusbar.policy.Clock
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.window.StatusBarWindowStateController
@@ -98,7 +97,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Mock private lateinit var windowRootView: Provider<WindowRootView>
@Mock private lateinit var shadeLogger: ShadeLogger
@Mock private lateinit var viewUtil: ViewUtil
- @Mock private lateinit var longPressGestureDetector: LongPressGestureDetector
+ @Mock private lateinit var mStatusBarLongPressGestureDetector: StatusBarLongPressGestureDetector
private lateinit var statusBarWindowStateController: StatusBarWindowStateController
private lateinit var view: PhoneStatusBarView
@@ -395,7 +394,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
shadeControllerImpl,
shadeViewController,
panelExpansionInteractor,
- { longPressGestureDetector },
+ { mStatusBarLongPressGestureDetector },
windowRootView,
shadeLogger,
viewUtil,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 4d293b98c165..6326e73aec67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -102,6 +102,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
val connectionState by collectLastValue(underTest.connectionState)
@@ -267,11 +268,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteProvisioned_notSupported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
}
@@ -280,11 +277,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteProvisioned_supported_defaultFalse() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
// THEN default provisioned state is false
assertThat(underTest.isSatelliteProvisioned.value).isFalse()
@@ -323,6 +316,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -361,6 +355,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
// WHEN we try to check for provisioned status
@@ -445,11 +440,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteProvisioned_supported_tracksCallback_reRegistersOnCrash() =
testScope.runTest {
// GIVEN satellite is supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true)
val provisioned by collectLastValue(underTest.isSatelliteProvisioned)
@@ -487,11 +478,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteNotSupported_listenersAreNotRegistered() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
// WHEN data is requested from the repo
val connectionState by collectLastValue(underTest.connectionState)
@@ -517,11 +504,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteNotSupported_registersCallbackForStateChanges() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
// THEN the repo registers for state changes of satellite support
@@ -577,11 +560,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun satelliteNotSupported_supportShowsUp_registersListeners() =
testScope.runTest {
// GIVEN satellite is not supported
- setUpRepo(
- uptime = MIN_UPTIME,
- satMan = satelliteManager,
- satelliteSupported = false,
- )
+ setUpRepo(uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = false)
runCurrent()
val callback =
@@ -610,11 +589,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
fun repoDoesNotCheckForSupportUntilMinUptime() =
testScope.runTest {
// GIVEN we init 100ms after sysui starts up
- setUpRepo(
- uptime = 100,
- satMan = satelliteManager,
- satelliteSupported = true,
- )
+ setUpRepo(uptime = 100, satMan = satelliteManager, satelliteSupported = true)
// WHEN data is requested
val connectionState by collectLastValue(underTest.connectionState)
@@ -726,6 +701,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
logBuffer = FakeLogBuffer.Factory.create(),
verboseLogBuffer = FakeLogBuffer.Factory.create(),
systemClock,
+ context.resources,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
new file mode 100644
index 000000000000..778614b79fc8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/OnTeardownRule.kt
@@ -0,0 +1,75 @@
+/*
+ * 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
+
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import org.junit.runners.model.MultipleFailureException
+
+/**
+ * Rule that allows teardown steps to be added right next to the places where it becomes clear they
+ * are needed. This can avoid the need for complicated or conditional logic in a single teardown
+ * method. Examples:
+ * ```
+ * @get:Rule teardownRule = OnTeardownRule()
+ *
+ * // setup and teardown right next to each other
+ * @Before
+ * fun setUp() {
+ * val oldTimeout = getGlobalTimeout()
+ * teardownRule.onTeardown { setGlobalTimeout(oldTimeout) }
+ * overrideGlobalTimeout(5000)
+ * }
+ *
+ * // add teardown logic for fixtures that aren't used in every test
+ * fun addCustomer() {
+ * val id = globalDatabase.addCustomer(TEST_NAME, TEST_ADDRESS, ...)
+ * teardownRule.onTeardown { globalDatabase.deleteCustomer(id) }
+ * }
+ * ```
+ */
+class OnTeardownRule : TestWatcher() {
+ private var canAdd = true
+ private val teardowns = mutableListOf<() -> Unit>()
+
+ fun onTeardown(teardownRunnable: () -> Unit) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add(teardownRunnable)
+ }
+
+ fun onTeardown(teardownRunnable: Runnable) {
+ if (!canAdd) {
+ throw IllegalStateException("Cannot add new teardown routines after test complete.")
+ }
+ teardowns.add { teardownRunnable.run() }
+ }
+
+ override fun finished(description: Description?) {
+ canAdd = false
+ val errors = mutableListOf<Throwable>()
+ teardowns.reversed().forEach {
+ try {
+ it()
+ } catch (e: Throwable) {
+ errors.add(e)
+ }
+ }
+ MultipleFailureException.assertEmpty(errors)
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 27a2cab1448e..153a8be06adc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -56,6 +56,8 @@ import org.mockito.Mockito;
import java.io.FileInputStream;
import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
@@ -69,6 +71,17 @@ import java.util.concurrent.Future;
// background on Ravenwood is available at go/ravenwood-docs
@DisabledOnRavenwood
public abstract class SysuiTestCase {
+ /**
+ * Especially when self-testing test utilities, we may have classes that look like test
+ * classes, but we don't expect to ever actually run as a top-level test.
+ * For example, {@link com.android.systemui.TryToDoABadThing}.
+ * Verifying properties on these as a part of structural tests like
+ * AAAPlusPlusVerifySysuiRequiredTestPropertiesTest is a waste of our time, and makes things
+ * look more confusing, so this lets us skip when appropriate.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface SkipSysuiVerification {
+ }
private static final String TAG = "SysuiTestCase";
@@ -172,6 +185,15 @@ public abstract class SysuiTestCase {
public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
new DexmakerShareClassLoaderRule();
+ @Rule public final OnTeardownRule mTearDownRule = new OnTeardownRule();
+
+ /**
+ * Schedule a cleanup routine to happen when the test state is torn down.
+ */
+ protected void onTeardown(Runnable tearDownRunnable) {
+ mTearDownRule.onTeardown(tearDownRunnable);
+ }
+
// set the highest order so it's the innermost rule
@Rule(order = Integer.MAX_VALUE)
public TestWithLooperRule mlooperRule = new TestWithLooperRule();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index b27dadc9035b..3b175853de7d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -42,15 +42,6 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
val id = nextWidgetId++
val providerInfo = createAppWidgetProviderInfo(provider, user.identifier)
- fakeDatabase[id] =
- CommunalWidgetContentModel.Available(
- appWidgetId = id,
- rank = rank ?: 0,
- providerInfo = providerInfo,
- spanY = 3,
- )
- updateListFromDatabase()
-
val configured = configurator?.configureWidget(id) != false
if (configured) {
onConfigured(id, providerInfo, rank ?: -1)
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 ddae58168201..72cb1dfe38db 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,8 +1,10 @@
package com.android.systemui.kosmos
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos.Fixture
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -45,3 +47,5 @@ fun Kosmos.runTest(testBody: suspend Kosmos.() -> Unit) =
testScope.runTest { this@runTest.testBody() }
fun Kosmos.runCurrent() = testScope.runCurrent()
+
+fun <T> Kosmos.collectLastValue(flow: Flow<T>) = testScope.collectLastValue(flow)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
index 5eaa198fb2a6..63e6eb6c0ef5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -51,6 +51,8 @@ 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.pulseExpansionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.lockscreenToGlanceableHubTransitionViewModel
import com.android.systemui.model.sceneContainerPlugin
import com.android.systemui.plugins.statusbar.statusBarStateController
import com.android.systemui.power.data.repository.fakePowerRepository
@@ -68,6 +70,8 @@ import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.shade.shadeController
import com.android.systemui.shade.ui.viewmodel.notificationShadeWindowModel
import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.seenNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
@@ -106,6 +110,7 @@ class KosmosJavaAdapter() {
val bouncerRepository by lazy { kosmos.bouncerRepository }
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
+ val activeNotificationsInteractor by lazy { kosmos.activeNotificationsInteractor }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
val seenNotificationsInteractor by lazy { kosmos.seenNotificationsInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
@@ -119,6 +124,7 @@ class KosmosJavaAdapter() {
val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val statusBarModePerDisplayRepository by lazy { kosmos.fakeStatusBarModePerDisplayRepository }
val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
val sceneInteractor by lazy { kosmos.sceneInteractor }
@@ -164,4 +170,11 @@ class KosmosJavaAdapter() {
val msdlPlayer by lazy { kosmos.fakeMSDLPlayer }
val shadeModeInteractor by lazy { kosmos.shadeModeInteractor }
val bouncerHapticHelper by lazy { kosmos.bouncerHapticPlayer }
+
+ val glanceableHubToLockscreenTransitionViewModel by lazy {
+ kosmos.glanceableHubToLockscreenTransitionViewModel
+ }
+ val lockscreenToGlanceableHubTransitionViewModel by lazy {
+ kosmos.lockscreenToGlanceableHubTransitionViewModel
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
new file mode 100644
index 000000000000..7e7eea216584
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/Media3ActionFactoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.domain.pipeline
+
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.media3ActionFactory: Media3ActionFactory by Kosmos.Fixture { mock {} }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
index cb7750f55647..af6a0c505535 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/domain/pipeline/MediaDataLoaderKosmos.kt
@@ -34,6 +34,7 @@ val Kosmos.mediaDataLoader by
fakeMediaControllerFactory,
mediaFlags,
imageLoader,
- statusBarManager
+ statusBarManager,
+ media3ActionFactory,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
index 7f8348e2ca6f..b833750a2c4a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeMediaControllerFactory.kt
@@ -18,21 +18,32 @@ package com.android.systemui.media.controls.util
import android.content.Context
import android.media.session.MediaController
-import android.media.session.MediaSession
import android.media.session.MediaSession.Token
+import android.os.Looper
+import androidx.media3.session.MediaController as Media3Controller
+import androidx.media3.session.SessionToken
class FakeMediaControllerFactory(context: Context) : MediaControllerFactory(context) {
private val mediaControllersForToken = mutableMapOf<Token, MediaController>()
+ private var media3Controller: Media3Controller? = null
- override fun create(token: MediaSession.Token): android.media.session.MediaController {
+ override fun create(token: Token): MediaController {
if (token !in mediaControllersForToken) {
super.create(token)
}
return mediaControllersForToken[token]!!
}
+ override suspend fun create(token: SessionToken, looper: Looper): Media3Controller {
+ return media3Controller ?: super.create(token, looper)
+ }
+
fun setControllerForToken(token: Token, mediaController: MediaController) {
mediaControllersForToken[token] = mediaController
}
+
+ fun setMedia3Controller(mediaController: Media3Controller) {
+ media3Controller = mediaController
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.kt
new file mode 100644
index 000000000000..94e0bca5675b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/FakeSessionTokenFactory.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.systemui.media.controls.util
+
+import android.content.Context
+import android.media.session.MediaSession.Token
+import androidx.media3.session.SessionToken
+
+class FakeSessionTokenFactory(context: Context) : SessionTokenFactory(context) {
+ private var sessionToken: SessionToken? = null
+
+ override suspend fun createTokenFromLegacy(token: Token): SessionToken {
+ return sessionToken ?: super.createTokenFromLegacy(token)
+ }
+
+ fun setMedia3SessionToken(token: SessionToken) {
+ sessionToken = token
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
new file mode 100644
index 000000000000..8e473042c5d4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/media/controls/util/SessionTokenFactoryKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.controls.util
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeSessionTokenFactory by Kosmos.Fixture { FakeSessionTokenFactory(applicationContext) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
index f66125a6087e..6787b8ebb37f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/di/NewQSTileFactoryKosmos.kt
@@ -52,7 +52,7 @@ val Kosmos.customTileViewModelFactory: QSTileViewModelFactory.Component by
)
object : QSTileViewModel {
override val state: StateFlow<QSTileState?> =
- MutableStateFlow(QSTileState.build({ null }, tileSpec.spec) {})
+ MutableStateFlow(QSTileState.build(null, tileSpec.spec) {})
override val config: QSTileConfig = config
override val isAvailable: StateFlow<Boolean> = MutableStateFlow(true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
index 5b6fd8c3bd62..ab1c1818bf80 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/QSTileStateSubject.kt
@@ -44,7 +44,7 @@ private constructor(failureMetadata: FailureMetadata, subject: QSTileState?) :
check("other").that(other).isNotNull()
other ?: return
}
- check("icon").that(actual.icon()).isEqualTo(other.icon())
+ check("icon").that(actual.icon).isEqualTo(other.icon)
check("iconRes").that(actual.iconRes).isEqualTo(other.iconRes)
check("label").that(actual.label).isEqualTo(other.label)
check("activationState").that(actual.activationState).isEqualTo(other.activationState)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
index 4228c3c0b110..7e6a7271c561 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt
@@ -32,7 +32,6 @@ import com.android.systemui.haptics.vibratorHelper
import com.android.systemui.keyguard.dismissCallbackRegistry
import com.android.systemui.keyguard.domain.interactor.keyguardEnabledInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.kosmos.testScope
@@ -78,7 +77,6 @@ val Kosmos.sceneContainerStartable by Fixture {
uiEventLogger = uiEventLogger,
sceneBackInteractor = sceneBackInteractor,
shadeSessionStorage = shadeSessionStorage,
- windowMgrLockscreenVisInteractor = windowManagerLockscreenVisibilityInteractor,
keyguardEnabledInteractor = keyguardEnabledInteractor,
dismissCallbackRegistry = dismissCallbackRegistry,
statusBarStateController = sysuiStatusBarStateController,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
index 8c218be6c982..50a19a9bc68a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt
@@ -16,11 +16,13 @@
package com.android.systemui.statusbar.core
+import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository
import com.android.systemui.statusbar.window.StatusBarWindowController
class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory {
override fun create(
- statusBarWindowController: StatusBarWindowController
+ statusBarWindowController: StatusBarWindowController,
+ statusBarModePerDisplayRepository: StatusBarModePerDisplayRepository,
): StatusBarInitializer = FakeStatusBarInitializer()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
index 303529b7f7b0..6e990277df6b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.core
import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository
import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore
val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() }
@@ -37,6 +38,7 @@ val Kosmos.multiDisplayStatusBarInitializerStore by
displayRepository,
fakeStatusBarInitializerFactory,
fakeStatusBarWindowControllerStore,
+ fakeStatusBarModeRepository,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
index 285cebb96cae..8712b02ec884 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt
@@ -20,8 +20,10 @@ import android.view.Display
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.data.model.StatusBarAppearance
import com.android.systemui.statusbar.data.model.StatusBarMode
+import com.android.systemui.statusbar.phone.fragment.dagger.HomeStatusBarComponent
import dagger.Binds
import dagger.Module
+import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
@@ -53,6 +55,14 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository
override fun clearTransient() {
isTransientShown.value = false
}
+
+ override fun start() {}
+
+ override fun stop() {}
+
+ override fun onStatusBarViewInitialized(component: HomeStatusBarComponent) {}
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {}
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
new file mode 100644
index 000000000000..5f337326b546
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/LightBarControllerStoreKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.statusbar.data.repository
+
+import com.android.systemui.display.data.repository.displayRepository
+import com.android.systemui.display.data.repository.displayScopeRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
+
+val Kosmos.lightBarControllerStoreImpl by
+ Kosmos.Fixture {
+ LightBarControllerStoreImpl(
+ backgroundApplicationScope = applicationCoroutineScope,
+ displayRepository = displayRepository,
+ factory = { _, _, _ -> mock() },
+ displayScopeRepository = displayScopeRepository,
+ statusBarModeRepositoryStore = statusBarModeRepository,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
index 12db2f74197d..a5856020d6a4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt
@@ -16,7 +16,10 @@
package com.android.systemui.statusbar.data.repository
+import com.android.systemui.display.data.repository.displayRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import org.mockito.kotlin.mock
val Kosmos.fakeStatusBarModePerDisplayRepository by
Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() }
@@ -24,3 +27,21 @@ val Kosmos.fakeStatusBarModePerDisplayRepository by
val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by
Kosmos.Fixture { fakeStatusBarModeRepository }
val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() }
+val Kosmos.fakeStatusBarModePerDisplayRepositoryFactory by
+ Kosmos.Fixture { FakeStatusBarModePerDisplayRepositoryFactory() }
+
+val Kosmos.multiDisplayStatusBarModeRepositoryStore by
+ Kosmos.Fixture {
+ MultiDisplayStatusBarModeRepositoryStore(
+ applicationCoroutineScope,
+ fakeStatusBarModePerDisplayRepositoryFactory,
+ displayRepository,
+ )
+ }
+
+class FakeStatusBarModePerDisplayRepositoryFactory : StatusBarModePerDisplayRepositoryFactory {
+
+ override fun create(displayId: Int): StatusBarModePerDisplayRepositoryImpl {
+ return mock<StatusBarModePerDisplayRepositoryImpl>()
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
index 76bdc0de3d7b..32c582f79ed7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/data/model/ActiveNotificationModelBuilder.kt
@@ -28,6 +28,7 @@ fun activeNotificationModel(
key: String,
groupKey: String? = null,
whenTime: Long = 0L,
+ isPromoted: Boolean = false,
isAmbient: Boolean = false,
isRowDismissed: Boolean = false,
isSilent: Boolean = false,
@@ -50,6 +51,7 @@ fun activeNotificationModel(
key = key,
groupKey = groupKey,
whenTime = whenTime,
+ isPromoted = isPromoted,
isAmbient = isAmbient,
isRowDismissed = isRowDismissed,
isSilent = isSilent,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
index f7acae9846df..067193fb7aa9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt
@@ -19,8 +19,13 @@ package com.android.systemui.statusbar.notification.domain.interactor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider
import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
+import com.android.systemui.statusbar.notification.promoted.promotedNotificationsProvider
val Kosmos.renderNotificationListInteractor by
Kosmos.Fixture {
- RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider)
+ RenderNotificationListInteractor(
+ activeNotificationListRepository,
+ sectionStyleProvider,
+ promotedNotificationsProvider,
+ )
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
index 94b2bdf63608..a7aa0b41a7aa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/UserAwareSecureSettingsRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationsProviderKosmos.kt
@@ -14,9 +14,8 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.statusbar.notification.promoted
import com.android.systemui.kosmos.Kosmos
-val Kosmos.userAwareSecureSettingsRepository by
- Kosmos.Fixture { FakeUserAwareSecureSettingsRepository() }
+val Kosmos.promotedNotificationsProvider by Kosmos.Fixture { PromotedNotificationsProviderImpl() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
new file mode 100644
index 000000000000..01175a568b3a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/viewmodel/StatusBarUserChipViewModelKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.statusbar.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.user.domain.interactor.userSwitcherInteractor
+import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel
+
+val Kosmos.statusBarUserChipViewModel: StatusBarUserChipViewModel by
+ Kosmos.Fixture { StatusBarUserChipViewModel(userSwitcherInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
new file mode 100644
index 000000000000..bf66cb6e8ecc
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/ExecutionKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.util.concurrency
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeExecution: FakeExecution by
+ Kosmos.Fixture { FakeExecution().apply { simulateMainThread = false } }
+var Kosmos.execution: Execution by Kosmos.Fixture { fakeExecution }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
index 5054e29534e9..dc10ca9fd0a5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeUserAwareSecureSettingsRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSecureSettingsRepositoryKosmos.kt
@@ -14,22 +14,21 @@
* limitations under the License.
*/
-package com.android.systemui.util.settings
+package com.android.systemui.util.settings.data.repository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.map
-class FakeUserAwareSecureSettingsRepository : UserAwareSecureSettingsRepository {
-
- private val settings = MutableStateFlow<Map<String, Boolean>>(mutableMapOf())
-
- override fun boolSettingForActiveUser(name: String, defaultValue: Boolean): Flow<Boolean> {
- return settings.map { it.getOrDefault(name, defaultValue) }
- }
-
- fun setBoolSettingForActiveUser(name: String, value: Boolean) {
- settings.value = settings.value.toMutableMap().apply { this[name] = value }
+val Kosmos.userAwareSecureSettingsRepository by
+ Kosmos.Fixture {
+ UserAwareSecureSettingsRepository(
+ fakeSettings,
+ userRepository,
+ testDispatcher,
+ backgroundCoroutineContext,
+ )
}
-} \ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
new file mode 100644
index 000000000000..ff77908a9b94
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/data/repository/UserAwareSystemSettingsRepositoryKosmos.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.util.settings.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundCoroutineContext
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.user.data.repository.userRepository
+import com.android.systemui.util.settings.fakeSettings
+import com.android.systemui.util.settings.repository.UserAwareSystemSettingsRepository
+
+val Kosmos.userAwareSystemSettingsRepository by
+ Kosmos.Fixture {
+ UserAwareSystemSettingsRepository(
+ fakeSettings,
+ userRepository,
+ testDispatcher,
+ backgroundCoroutineContext,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
index 1b58582a806f..ed5322ed098e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/MediaOutputKosmos.kt
@@ -21,6 +21,7 @@ import android.content.pm.ApplicationInfo
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.mediaOutputDialogManager
+import com.android.systemui.util.concurrency.execution
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -53,6 +54,7 @@ val Kosmos.mediaOutputInteractor by
testScope.testScheduler,
mediaControllerRepository,
mediaControllerInteractor,
+ execution,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
index 652b3ea984e7..fdeb8cef02b9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaControllerInteractorKosmos.kt
@@ -19,6 +19,7 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto
import android.os.Handler
import android.os.looper
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
var Kosmos.mediaControllerInteractor: MediaControllerInteractor by
- Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper)) }
+ Kosmos.Fixture { MediaControllerInteractorImpl(Handler(looper), testScope.testScheduler) }
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
index 5075f6322d1b..44abb9ffb34f 100644
--- a/packages/overlays/Android.bp
+++ b/packages/overlays/Android.bp
@@ -21,6 +21,7 @@ package {
phony {
name: "frameworks-base-overlays",
+ product_specific: true,
required: [
"DisplayCutoutEmulationCornerOverlay",
"DisplayCutoutEmulationDoubleOverlay",
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
index 66a6890a23b0..869d854f7b23 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodAwareTestRunner.java
@@ -26,12 +26,10 @@ import android.annotation.Nullable;
import android.os.Bundle;
import android.platform.test.annotations.RavenwoodTestRunnerInitializing;
import android.platform.test.annotations.internal.InnerRunner;
-import android.platform.test.ravenwood.RavenwoodTestStats.Result;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.AssumptionViolatedException;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.Runner;
@@ -171,10 +169,11 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
final var notifier = new RavenwoodRunNotifier(realNotifier);
final var description = getDescription();
+ RavenwoodTestStats.getInstance().attachToRunNotifier(notifier);
+
if (mRealRunner instanceof ClassSkippingTestRunner) {
- mRealRunner.run(notifier);
Log.i(TAG, "onClassSkipped: description=" + description);
- RavenwoodTestStats.getInstance().onClassSkipped(description);
+ mRealRunner.run(notifier);
return;
}
@@ -205,7 +204,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
if (!skipRunnerHook) {
try {
- RavenwoodTestStats.getInstance().onClassFinished(description);
mState.exitTestClass();
} catch (Throwable th) {
notifier.reportAfterTestFailure(th);
@@ -295,8 +293,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
// method-level annotations here.
if (scope == Scope.Instance && order == Order.Outer) {
if (!RavenwoodEnablementChecker.shouldEnableOnRavenwood(description, true)) {
- RavenwoodTestStats.getInstance().onTestFinished(
- classDescription, description, Result.Skipped);
return false;
}
}
@@ -317,16 +313,6 @@ public final class RavenwoodAwareTestRunner extends RavenwoodAwareTestRunnerBase
// End of a test method.
mState.exitTestMethod();
- final Result result;
- if (th == null) {
- result = Result.Passed;
- } else if (th instanceof AssumptionViolatedException) {
- result = Result.Skipped;
- } else {
- result = Result.Failed;
- }
-
- RavenwoodTestStats.getInstance().onTestFinished(classDescription, description, result);
}
// If RUN_DISABLED_TESTS is set, and the method did _not_ throw, make it an error.
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
index 0f163524d2fe..28c262d53ff1 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java
@@ -40,6 +40,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Process_ravenwood;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.DeviceConfig_host;
@@ -52,6 +53,7 @@ import androidx.test.platform.app.InstrumentationRegistry;
import com.android.hoststubgen.hosthelper.HostTestUtils;
import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
@@ -199,7 +201,7 @@ public class RavenwoodRuntimeEnvironmentController {
*/
public static void init(RavenwoodAwareTestRunner runner) {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "init() called here: " + runner, new RuntimeException("STACKTRACE"));
}
if (sRunner == runner) {
return;
@@ -223,7 +225,9 @@ public class RavenwoodRuntimeEnvironmentController {
Thread.setDefaultUncaughtExceptionHandler(sUncaughtExceptionHandler);
}
- android.os.Process.init$ravenwood(config.mUid, config.mPid);
+ RavenwoodRuntimeState.sUid = config.mUid;
+ RavenwoodRuntimeState.sPid = config.mPid;
+ RavenwoodRuntimeState.sTargetSdkLevel = config.mTargetSdkLevel;
sOriginalIdentityToken = Binder.clearCallingIdentity();
reinit();
setSystemProperties(config.mSystemProperties);
@@ -310,7 +314,7 @@ public class RavenwoodRuntimeEnvironmentController {
*/
public static void reset() {
if (RAVENWOOD_VERBOSE_LOGGING) {
- Log.i(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
+ Log.v(TAG, "reset() called here", new RuntimeException("STACKTRACE"));
}
if (sRunner == null) {
throw new RavenwoodRuntimeException("Internal error: reset() already called");
@@ -350,8 +354,8 @@ public class RavenwoodRuntimeEnvironmentController {
if (sOriginalIdentityToken != -1) {
Binder.restoreCallingIdentity(sOriginalIdentityToken);
}
- android.os.Process.reset$ravenwood();
-
+ RavenwoodRuntimeState.reset();
+ Process_ravenwood.reset();
DeviceConfig_host.reset();
try {
diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
index 016de8e45291..787058545fed 100644
--- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
+++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodTestStats.java
@@ -18,6 +18,9 @@ package android.platform.test.ravenwood;
import android.util.Log;
import org.junit.runner.Description;
+import org.junit.runner.notification.Failure;
+import org.junit.runner.notification.RunListener;
+import org.junit.runner.notification.RunNotifier;
import java.io.File;
import java.io.IOException;
@@ -27,7 +30,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
@@ -39,7 +42,7 @@ import java.util.Map;
*/
public class RavenwoodTestStats {
private static final String TAG = "RavenwoodTestStats";
- private static final String HEADER = "Module,Class,ClassDesc,Passed,Failed,Skipped";
+ private static final String HEADER = "Module,Class,OuterClass,Passed,Failed,Skipped";
private static RavenwoodTestStats sInstance;
@@ -66,7 +69,7 @@ public class RavenwoodTestStats {
private final PrintWriter mOutputWriter;
private final String mTestModuleName;
- public final Map<Description, Map<Description, Result>> mStats = new HashMap<>();
+ public final Map<String, Map<String, Result>> mStats = new LinkedHashMap<>();
/** Ctor */
public RavenwoodTestStats() {
@@ -115,75 +118,129 @@ public class RavenwoodTestStats {
return cwd.getName();
}
- private void addResult(Description classDescription, Description methodDescription,
+ private void addResult(String className, String methodName,
Result result) {
- mStats.compute(classDescription, (classDesc, value) -> {
+ mStats.compute(className, (className_, value) -> {
if (value == null) {
- value = new HashMap<>();
+ value = new LinkedHashMap<>();
+ }
+ // If the result is already set, don't overwrite it.
+ if (!value.containsKey(methodName)) {
+ value.put(methodName, result);
}
- value.put(methodDescription, result);
return value;
});
}
/**
- * Call it when a test class is skipped.
- */
- public void onClassSkipped(Description classDescription) {
- addResult(classDescription, Description.EMPTY, Result.Skipped);
- onClassFinished(classDescription);
- }
-
- /**
* Call it when a test method is finished.
*/
- public void onTestFinished(Description classDescription, Description testDescription,
- Result result) {
- addResult(classDescription, testDescription, result);
+ private void onTestFinished(String className, String testName, Result result) {
+ addResult(className, testName, result);
}
/**
- * Call it when a test class is finished.
+ * Dump all the results and clear it.
*/
- public void onClassFinished(Description classDescription) {
- int passed = 0;
- int skipped = 0;
- int failed = 0;
- var stats = mStats.get(classDescription);
- if (stats == null) {
- return;
- }
- for (var e : stats.values()) {
- switch (e) {
- case Passed: passed++; break;
- case Skipped: skipped++; break;
- case Failed: failed++; break;
+ private void dumpAllAndClear() {
+ for (var entry : mStats.entrySet()) {
+ int passed = 0;
+ int skipped = 0;
+ int failed = 0;
+ var className = entry.getKey();
+
+ for (var e : entry.getValue().values()) {
+ switch (e) {
+ case Passed:
+ passed++;
+ break;
+ case Skipped:
+ skipped++;
+ break;
+ case Failed:
+ failed++;
+ break;
+ }
}
+
+ mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
+ mTestModuleName, className, getOuterClassName(className),
+ passed, failed, skipped);
}
+ mOutputWriter.flush();
+ mStats.clear();
+ }
- var testClass = extractTestClass(classDescription);
+ private static String getOuterClassName(String className) {
+ // Just delete the '$', because I'm not sure if the className we get here is actaully a
+ // valid class name that does exist. (it might have a parameter name, etc?)
+ int p = className.indexOf('$');
+ if (p < 0) {
+ return className;
+ }
+ return className.substring(0, p);
+ }
- mOutputWriter.printf("%s,%s,%s,%d,%d,%d\n",
- mTestModuleName, (testClass == null ? "?" : testClass.getCanonicalName()),
- classDescription, passed, failed, skipped);
- mOutputWriter.flush();
+ public void attachToRunNotifier(RunNotifier notifier) {
+ notifier.addListener(mRunListener);
}
- /**
- * Try to extract the class from a description, which is needed because
- * ParameterizedAndroidJunit4's description doesn't contain a class.
- */
- private Class<?> extractTestClass(Description desc) {
- if (desc.getTestClass() != null) {
- return desc.getTestClass();
+ private final RunListener mRunListener = new RunListener() {
+ @Override
+ public void testSuiteStarted(Description description) {
+ Log.d(TAG, "testSuiteStarted: " + description);
}
- // Look into the children.
- for (var child : desc.getChildren()) {
- var fromChild = extractTestClass(child);
- if (fromChild != null) {
- return fromChild;
- }
+
+ @Override
+ public void testSuiteFinished(Description description) {
+ Log.d(TAG, "testSuiteFinished: " + description);
}
- return null;
- }
+
+ @Override
+ public void testRunStarted(Description description) {
+ Log.d(TAG, "testRunStarted: " + description);
+ }
+
+ @Override
+ public void testRunFinished(org.junit.runner.Result result) {
+ Log.d(TAG, "testRunFinished: " + result);
+
+ dumpAllAndClear();
+ }
+
+ @Override
+ public void testStarted(Description description) {
+ Log.d(TAG, " testStarted: " + description);
+ }
+
+ @Override
+ public void testFinished(Description description) {
+ Log.d(TAG, " testFinished: " + description);
+
+ // Send "Passed", but if there's already another result sent for this, this won't
+ // override it.
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Passed);
+ }
+
+ @Override
+ public void testFailure(Failure failure) {
+ Log.d(TAG, " testFailure: " + failure);
+
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Failed);
+ }
+
+ @Override
+ public void testAssumptionFailure(Failure failure) {
+ Log.d(TAG, " testAssumptionFailure: " + failure);
+ var description = failure.getDescription();
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+
+ @Override
+ public void testIgnored(Description description) {
+ Log.d(TAG, " testIgnored: " + description);
+ onTestFinished(description.getClassName(), description.getMethodName(), Result.Skipped);
+ }
+ };
}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
index 37b0abcceede..d8f2b705d539 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java
@@ -23,6 +23,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Instrumentation;
import android.content.Context;
+import android.os.Build;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
@@ -67,7 +68,7 @@ public final class RavenwoodConfig {
String mTargetPackageName;
int mMinSdkLevel;
- int mTargetSdkLevel;
+ int mTargetSdkLevel = Build.VERSION_CODES.CUR_DEVELOPMENT;
boolean mProvideMainThread = false;
diff --git a/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
new file mode 100644
index 000000000000..3c6a4d78d1d9
--- /dev/null
+++ b/ravenwood/runtime-helper-src/framework/android/os/Process_ravenwood.java
@@ -0,0 +1,70 @@
+/*
+ * 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.os;
+
+import android.util.Pair;
+
+public class Process_ravenwood {
+
+ private static volatile ThreadLocal<Pair<Integer, Boolean>> sThreadPriority;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ // Reset the thread local variable
+ sThreadPriority = ThreadLocal.withInitial(
+ () -> Pair.create(Process.THREAD_PRIORITY_DEFAULT, true));
+ }
+
+ /**
+ * Called by {@link Process#setThreadPriority(int, int)}
+ */
+ public static void setThreadPriority(int tid, int priority) {
+ if (Process.myTid() == tid) {
+ boolean backgroundOk = sThreadPriority.get().second;
+ if (priority >= Process.THREAD_PRIORITY_BACKGROUND && !backgroundOk) {
+ throw new IllegalArgumentException(
+ "Priority " + priority + " blocked by setCanSelfBackground()");
+ }
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+
+ /**
+ * Called by {@link Process#setCanSelfBackground(boolean)}
+ */
+ public static void setCanSelfBackground(boolean backgroundOk) {
+ int priority = sThreadPriority.get().first;
+ sThreadPriority.set(Pair.create(priority, backgroundOk));
+ }
+
+ /**
+ * Called by {@link Process#getThreadPriority(int)}
+ */
+ public static int getThreadPriority(int tid) {
+ if (Process.myTid() == tid) {
+ return sThreadPriority.get().first;
+ } else {
+ throw new UnsupportedOperationException(
+ "Cross-thread priority management not yet available in Ravenwood");
+ }
+ }
+}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
deleted file mode 100644
index c18c307ad1e3..000000000000
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayContainer_host.java
+++ /dev/null
@@ -1,63 +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.internal.os;
-
-import java.util.Arrays;
-import java.util.HashMap;
-
-public class LongArrayContainer_host {
- private static final HashMap<Long, long[]> sInstances = new HashMap<>();
- private static long sNextId = 1;
-
- public static long native_init(int arrayLength) {
- long[] array = new long[arrayLength];
- long instanceId = sNextId++;
- sInstances.put(instanceId, array);
- return instanceId;
- }
-
- static long[] getInstance(long instanceId) {
- return sInstances.get(instanceId);
- }
-
- public static void native_setValues(long instanceId, long[] values) {
- System.arraycopy(values, 0, getInstance(instanceId), 0, values.length);
- }
-
- public static void native_getValues(long instanceId, long[] values) {
- System.arraycopy(getInstance(instanceId), 0, values, 0, values.length);
- }
-
- public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) {
- long[] values = getInstance(instanceId);
-
- boolean nonZero = false;
- Arrays.fill(array, 0);
-
- for (int i = 0; i < values.length; i++) {
- int index = indexMap[i];
- if (index < 0 || index >= array.length) {
- throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, "
- + (array.length - 1) + "]");
- }
- if (values[i] != 0) {
- array[index] += values[i];
- nonZero = true;
- }
- }
- return nonZero;
- }
-}
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
index 9ce8ea8e16ef..90608f6e87d1 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/os/LongArrayMultiStateCounter_host.java
@@ -286,15 +286,12 @@ public class LongArrayMultiStateCounter_host {
return getInstance(instanceId).mArrayLength;
}
- public static void native_setValues(long instanceId, int state, long containerInstanceId) {
- getInstance(instanceId).setValue(state,
- LongArrayContainer_host.getInstance(containerInstanceId));
+ public static void native_setValues(long instanceId, int state, long[] values) {
+ getInstance(instanceId).setValue(state, values);
}
- public static void native_updateValues(long instanceId, long containerInstanceId,
- long timestampMs) {
- getInstance(instanceId).updateValue(
- LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ public static void native_updateValues(long instanceId, long[] values, long timestampMs) {
+ getInstance(instanceId).updateValue(values, timestampMs);
}
public static void native_setState(long instanceId, int state, long timestampMs) {
@@ -305,19 +302,16 @@ public class LongArrayMultiStateCounter_host {
getInstance(targetInstanceId).copyStatesFrom(getInstance(sourceInstanceId));
}
- public static void native_incrementValues(long instanceId, long containerInstanceId,
- long timestampMs) {
- getInstance(instanceId).incrementValues(
- LongArrayContainer_host.getInstance(containerInstanceId), timestampMs);
+ public static void native_incrementValues(long instanceId, long[] delta, long timestampMs) {
+ getInstance(instanceId).incrementValues(delta, timestampMs);
}
- public static void native_addCounts(long instanceId, long containerInstanceId) {
- getInstance(instanceId).addCounts(LongArrayContainer_host.getInstance(containerInstanceId));
+ public static void native_addCounts(long instanceId, long[] counts) {
+ getInstance(instanceId).addCounts(counts);
}
- public static void native_getCounts(long instanceId, long containerInstanceId, int state) {
- getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId),
- state);
+ public static void native_getCounts(long instanceId, long[] counts, int state) {
+ getInstance(instanceId).getValues(counts, state);
}
public static void native_reset(long instanceId) {
diff --git a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
index e12ff240c4d9..b65668b67e03 100644
--- a/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
+++ b/ravenwood/runtime-helper-src/framework/com/android/internal/ravenwood/RavenwoodEnvironment_host.java
@@ -23,14 +23,6 @@ public class RavenwoodEnvironment_host {
}
/**
- * Called from {@link RavenwoodEnvironment#ensureRavenwoodInitialized()}.
- */
- public static void ensureRavenwoodInitialized() {
- // Initialization is now done by RavenwoodAwareTestRunner.
- // Should we remove it?
- }
-
- /**
* Called from {@link RavenwoodEnvironment#getRavenwoodRuntimePath()}.
*/
public static String getRavenwoodRuntimePath(RavenwoodEnvironment env) {
diff --git a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
index c94ef31a5e5e..02981713674d 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/android/system/Os.java
@@ -16,6 +16,7 @@
package android.system;
import com.android.ravenwood.RavenwoodRuntimeNative;
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.io.FileDescriptor;
@@ -97,4 +98,16 @@ public final class Os {
public static void setenv(String name, String value, boolean overwrite) throws ErrnoException {
RavenwoodRuntimeNative.setenv(name, value, overwrite);
}
+
+ public static int getpid() {
+ return RavenwoodRuntimeState.sPid;
+ }
+
+ public static int getuid() {
+ return RavenwoodRuntimeState.sUid;
+ }
+
+ public static int gettid() {
+ return RavenwoodRuntimeNative.gettid();
+ }
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
index f13189f6f8be..7b940b423b69 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeNative.java
@@ -58,6 +58,8 @@ public class RavenwoodRuntimeNative {
public static native void clearSystemProperties();
+ public static native int gettid();
+
public static long lseek(FileDescriptor fd, long offset, int whence) throws ErrnoException {
return nLseek(JvmWorkaround.getInstance().getFdInt(fd), offset, whence);
}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
new file mode 100644
index 000000000000..175e020d61d6
--- /dev/null
+++ b/ravenwood/runtime-helper-src/libcore-fake/com/android/ravenwood/RavenwoodRuntimeState.java
@@ -0,0 +1,35 @@
+/*
+ * 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.ravenwood;
+
+public class RavenwoodRuntimeState {
+ // This must match VMRuntime.SDK_VERSION_CUR_DEVELOPMENT.
+ public static final int CUR_DEVELOPMENT = 10000;
+
+ public static volatile int sUid;
+ public static volatile int sPid;
+ public static volatile int sTargetSdkLevel;
+
+ static {
+ reset();
+ }
+
+ public static void reset() {
+ sUid = -1;
+ sPid = -1;
+ sTargetSdkLevel = CUR_DEVELOPMENT;
+ }
+}
diff --git a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
index ba89f71dde8a..eaadac6a8b92 100644
--- a/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
+++ b/ravenwood/runtime-helper-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -19,6 +19,7 @@ package dalvik.system;
// The original is here:
// $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
+import com.android.ravenwood.RavenwoodRuntimeState;
import com.android.ravenwood.common.JvmWorkaround;
import java.lang.reflect.Array;
@@ -52,4 +53,8 @@ public class VMRuntime {
public long addressOf(Object obj) {
return JvmWorkaround.getInstance().addressOf(obj);
}
+
+ public int getTargetSdkVersion() {
+ return RavenwoodRuntimeState.sTargetSdkLevel;
+ }
}
diff --git a/ravenwood/runtime-jni/ravenwood_runtime.cpp b/ravenwood/runtime-jni/ravenwood_runtime.cpp
index 2a3c26ed3ea3..5b75e9854758 100644
--- a/ravenwood/runtime-jni/ravenwood_runtime.cpp
+++ b/ravenwood/runtime-jni/ravenwood_runtime.cpp
@@ -17,6 +17,7 @@
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
+#include <sys/syscall.h>
#include <unistd.h>
#include <utils/misc.h>
@@ -173,6 +174,12 @@ static void Linux_setenv(JNIEnv* env, jobject, jstring javaName, jstring javaVal
throwIfMinusOne(env, "setenv", setenv(name.c_str(), value.c_str(), overwrite ? 1 : 0));
}
+
+static jint Linux_gettid(JNIEnv* env, jobject) {
+ // gettid(2() was added in glibc 2.30 but Android uses an older version in prebuilt.
+ return syscall(__NR_gettid);
+}
+
static void maybeRedirectLog() {
auto ravenwoodLogOut = getenv("RAVENWOOD_LOG_OUT");
if (ravenwoodLogOut == NULL) {
@@ -207,6 +214,7 @@ static const JNINativeMethod sMethods[] =
{ "stat", "(Ljava/lang/String;)Landroid/system/StructStat;", (void*)Linux_stat },
{ "nOpen", "(Ljava/lang/String;II)I", (void*)Linux_open },
{ "setenv", "(Ljava/lang/String;Ljava/lang/String;Z)V", (void*)Linux_setenv },
+ { "gettid", "()I", (void*)Linux_gettid },
};
extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
diff --git a/ravenwood/scripts/run-ravenwood-tests.sh b/ravenwood/scripts/run-ravenwood-tests.sh
index 5d623e0b6c36..1910100a7f5d 100755
--- a/ravenwood/scripts/run-ravenwood-tests.sh
+++ b/ravenwood/scripts/run-ravenwood-tests.sh
@@ -18,12 +18,38 @@
# Options:
#
# -s: "Smoke" test -- skip slow tests (SysUI, ICU)
+#
+# -x PCRE: Specify exclusion filter in PCRE
+# Example: -x '^(Cts|hoststub)' # Exclude CTS and hoststubgen tests.
+#
+# -f PCRE: Specify inclusion filter in PCRE
+
+
+# Regex to identify slow tests, in PCRE
+SLOW_TEST_RE='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood|CarSystemUIRavenTests)$'
smoke=0
-while getopts "s" opt; do
+include_re=""
+exclude_re=""
+smoke_exclude_re=""
+dry_run=""
+while getopts "sx:f:d" opt; do
case "$opt" in
s)
- smoke=1
+ # Remove slow tests.
+ smoke_exclude_re="$SLOW_TEST_RE"
+ ;;
+ x)
+ # Take a PCRE from the arg, and use it as an exclusion filter.
+ exclude_re="$OPTARG"
+ ;;
+ f)
+ # Take a PCRE from the arg, and use it as an inclusion filter.
+ include_re="$OPTARG"
+ ;;
+ d)
+ # Dry run
+ dry_run="echo"
;;
'?')
exit 1
@@ -35,21 +61,46 @@ shift $(($OPTIND - 1))
all_tests=(hoststubgentest tiny-framework-dump-test hoststubgen-invoke-test ravenwood-stats-checker)
all_tests+=( $(${0%/*}/list-ravenwood-tests.sh) )
-# Regex to identify slow tests, in PCRE
-slow_tests_re='^(SystemUiRavenTests|CtsIcuTestCasesRavenwood)$'
-
-if (( $smoke )) ; then
- # Remove the slow tests.
- all_tests=( $(
- for t in "${all_tests[@]}"; do
- echo $t | grep -vP "$slow_tests_re"
- done
- ) )
-fi
+filter() {
+ local re="$1"
+ local grep_arg="$2"
+ if [[ "$re" == "" ]] ; then
+ cat # No filtering
+ else
+ grep $grep_arg -iP "$re"
+ fi
+}
-run() {
- echo "Running: $*"
- "${@}"
+filter_in() {
+ filter "$1"
}
-run ${ATEST:-atest} "${all_tests[@]}"
+filter_out() {
+ filter "$1" -v
+}
+
+
+# Remove the slow tests.
+targets=( $(
+ for t in "${all_tests[@]}"; do
+ echo $t | filter_in "$include_re" | filter_out "$smoke_exclude_re" | filter_out "$exclude_re"
+ done
+) )
+
+# Show the target tests
+
+echo "Target tests:"
+for t in "${targets[@]}"; do
+ echo " $t"
+done
+
+# Calculate the removed tests.
+
+diff="$(diff <(echo "${all_tests[@]}" | tr ' ' '\n') <(echo "${targets[@]}" | tr ' ' '\n') )"
+
+if [[ "$diff" != "" ]]; then
+ echo "Excluded tests:"
+ echo "$diff"
+fi
+
+$dry_run ${ATEST:-atest} "${targets[@]}"
diff --git a/ravenwood/tests/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp
index 410292001670..0c0df1f993aa 100644
--- a/ravenwood/tests/runtime-test/Android.bp
+++ b/ravenwood/tests/runtime-test/Android.bp
@@ -10,6 +10,9 @@ package {
android_ravenwood_test {
name: "RavenwoodRuntimeTest",
+ libs: [
+ "ravenwood-helper-runtime",
+ ],
static_libs: [
"androidx.annotation_annotation",
"androidx.test.ext.junit",
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
new file mode 100644
index 000000000000..8e04b698c9d9
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/IdentityTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.platform.test.ravenwood.RavenwoodConfig;
+import android.system.Os;
+
+import com.android.ravenwood.RavenwoodRuntimeState;
+
+import dalvik.system.VMRuntime;
+
+import org.junit.Test;
+
+public class IdentityTest {
+
+ @RavenwoodConfig.Config
+ public static final RavenwoodConfig sConfig =
+ new RavenwoodConfig.Builder()
+ .setTargetSdkLevel(UPSIDE_DOWN_CAKE)
+ .setProcessApp()
+ .build();
+
+ @Test
+ public void testUid() {
+ assertEquals(FIRST_APPLICATION_UID, RavenwoodRuntimeState.sUid);
+ assertEquals(FIRST_APPLICATION_UID, Os.getuid());
+ assertEquals(FIRST_APPLICATION_UID, Process.myUid());
+ assertEquals(FIRST_APPLICATION_UID, Binder.getCallingUid());
+ }
+
+ @Test
+ public void testPid() {
+ int pid = RavenwoodRuntimeState.sPid;
+ assertEquals(pid, Os.getpid());
+ assertEquals(pid, Process.myPid());
+ assertEquals(pid, Binder.getCallingPid());
+ }
+
+ @Test
+ public void testTargetSdkLevel() {
+ assertEquals(Build.VERSION_CODES.CUR_DEVELOPMENT, RavenwoodRuntimeState.CUR_DEVELOPMENT);
+ assertEquals(UPSIDE_DOWN_CAKE, RavenwoodRuntimeState.sTargetSdkLevel);
+ assertEquals(UPSIDE_DOWN_CAKE, VMRuntime.getRuntime().getTargetSdkVersion());
+ }
+}
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
index c2230c739ccf..c55506a0a10a 100644
--- a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java
@@ -24,6 +24,8 @@ import static android.system.OsConstants.S_ISREG;
import static android.system.OsConstants.S_ISSOCK;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
@@ -51,10 +53,12 @@ import java.nio.file.attribute.PosixFileAttributes;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class OsTest {
+
public interface ConsumerWithThrow<T> {
void accept(T var1) throws Exception;
}
@@ -165,6 +169,35 @@ public class OsTest {
});
}
+ private static class TestThread extends Thread {
+
+ final CountDownLatch mLatch = new CountDownLatch(1);
+ int mTid;
+
+ TestThread() {
+ setDaemon(true);
+ }
+
+ @Override
+ public void run() {
+ mTid = Os.gettid();
+ mLatch.countDown();
+ }
+ }
+
+ @Test
+ public void testGetTid() throws InterruptedException {
+ var t1 = new TestThread();
+ var t2 = new TestThread();
+ t1.start();
+ t2.start();
+ // Wait for thread execution
+ assertTrue(t1.mLatch.await(1, TimeUnit.SECONDS));
+ assertTrue(t2.mLatch.await(1, TimeUnit.SECONDS));
+ // Make sure the tid is unique per-thread
+ assertNotEquals(t1.mTid, t2.mTid);
+ }
+
// Verify StructStat values from libcore against native JVM PosixFileAttributes
private static void assertAttributesEqual(PosixFileAttributes attr, StructStat stat) {
assertEquals(attr.lastModifiedTime(), convertTimespecToFileTime(stat.st_mtim));
diff --git a/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
new file mode 100644
index 000000000000..d25b5c19f351
--- /dev/null
+++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/ProcessTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.ravenwoodtest.runtimetest;
+
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.THREAD_PRIORITY_DEFAULT;
+import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.os.Process;
+import android.system.Os;
+
+import org.junit.Test;
+
+public class ProcessTest {
+
+ @Test
+ public void testGetUidPidTid() {
+ assertEquals(Os.getuid(), Process.myUid());
+ assertEquals(Os.getpid(), Process.myPid());
+ assertEquals(Os.gettid(), Process.myTid());
+ }
+
+ @Test
+ public void testThreadPriority() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.getThreadPriority(Process.myTid() + 1));
+ assertThrows(UnsupportedOperationException.class,
+ () -> Process.setThreadPriority(Process.myTid() + 1, THREAD_PRIORITY_DEFAULT));
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ Process.setThreadPriority(THREAD_PRIORITY_FOREGROUND);
+ assertEquals(THREAD_PRIORITY_FOREGROUND, Process.getThreadPriority(Process.myTid()));
+ Process.setCanSelfBackground(false);
+ Process.setThreadPriority(THREAD_PRIORITY_DEFAULT);
+ assertEquals(THREAD_PRIORITY_DEFAULT, Process.getThreadPriority(Process.myTid()));
+ assertThrows(IllegalArgumentException.class,
+ () -> Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND));
+ }
+}
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 68ff9725ce5c..8567ccb9e7a5 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -17,6 +17,7 @@
package com.android.server.appwidget;
import static android.appwidget.flags.Flags.remoteAdapterConversion;
+import static android.appwidget.flags.Flags.remoteViewsProto;
import static android.appwidget.flags.Flags.removeAppWidgetServiceIoFromCriticalPath;
import static android.appwidget.flags.Flags.securityPolicyInteractAcrossUsers;
import static android.appwidget.flags.Flags.supportResumeRestoreAfterReboot;
@@ -31,6 +32,7 @@ import static com.android.server.appwidget.AppWidgetXmlUtil.serializeWidgetSizes
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import android.Manifest;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PermissionName;
@@ -104,6 +106,7 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.DeviceConfig;
import android.service.appwidget.AppWidgetServiceDumpProto;
+import android.service.appwidget.GeneratedPreviewsProto;
import android.service.appwidget.WidgetProto;
import android.text.TextUtils;
import android.util.ArrayMap;
@@ -122,7 +125,9 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import android.view.Display;
import android.view.View;
import android.widget.RemoteViews;
@@ -134,6 +139,7 @@ import com.android.internal.app.UnlaunchableAppActivity;
import com.android.internal.appwidget.IAppWidgetHost;
import com.android.internal.appwidget.IAppWidgetService;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
@@ -221,6 +227,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// XML attribute for widget ids that are pending deletion.
// See {@link Provider#pendingDeletedWidgetIds}.
private static final String PENDING_DELETED_IDS_ATTR = "pending_deleted_ids";
+ // Name of service directory in /data/system_ce/<user>/
+ private static final String APPWIDGET_CE_DATA_DIRNAME = "appwidget";
+ // Name of previews directory in /data/system_ce/<user>/appwidget/
+ private static final String WIDGET_PREVIEWS_DIRNAME = "previews";
// Hard limit of number of hosts an app can create, note that the app that hosts the widgets
// can have multiple instances of {@link AppWidgetHost}, typically in respect to different
@@ -320,6 +330,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
// Handler to the background thread that saves states to disk.
private Handler mSaveStateHandler;
+ // Handler to the background thread that saves generated previews to disk. All operations that
+ // modify saved previews must be run on this Handler.
+ private Handler mSavePreviewsHandler;
// Handler to the foreground thread that handles broadcasts related to user
// and package events, as well as various internal events within
// AppWidgetService.
@@ -363,6 +376,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
} else {
mSaveStateHandler = BackgroundThread.getHandler();
}
+ mSavePreviewsHandler = new Handler(BackgroundThread.get().getLooper());
final ServiceThread serviceThread = new ServiceThread(TAG,
android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
serviceThread.start();
@@ -382,7 +396,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
SystemUiDeviceConfigFlags.GENERATED_PREVIEW_API_MAX_PROVIDERS,
DEFAULT_GENERATED_PREVIEW_MAX_PROVIDERS);
mGeneratedPreviewsApiCounter = new ApiCounter(generatedPreviewResetInterval,
- generatedPreviewMaxCallsPerInterval, generatedPreviewsMaxProviders);
+ generatedPreviewMaxCallsPerInterval,
+ // Set a limit on the number of providers if storing them in memory.
+ remoteViewsProto() ? Integer.MAX_VALUE : generatedPreviewsMaxProviders);
DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
new HandlerExecutor(mCallbackHandler), this::handleSystemUiDeviceConfigChange);
@@ -648,7 +664,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
for (int i = 0; i < providerCount; i++) {
Provider provider = mProviders.get(i);
if (provider.id.uid == clearedUid) {
- changed |= provider.clearGeneratedPreviewsLocked();
+ if (remoteViewsProto()) {
+ changed |= clearGeneratedPreviewsAsync(provider);
+ } else {
+ changed |= provider.clearGeneratedPreviewsLocked();
+ }
+ if (DEBUG) {
+ Slog.e(TAG, "clearPreviewsForUidLocked " + provider + " changed " + changed);
+ }
}
}
return changed;
@@ -898,18 +921,30 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
for (int j = 0; j < widgetCount; j++) {
Widget widget = provider.widgets.get(j);
if (targetWidget != null && targetWidget != widget) continue;
+ // Identify the user in the host process since the intent will be invoked by
+ // the host app.
+ final Host host = widget.host;
+ final UserHandle hostUser;
+ if (host != null && host.id != null) {
+ hostUser = UserHandle.getUserHandleForUid(host.id.uid);
+ } else {
+ // Fallback to the parent profile if the host is null.
+ Slog.w(TAG, "Host is null when masking widget: " + widget.appWidgetId);
+ hostUser = mUserManager.getProfileParent(appUserId).getUserHandle();
+ }
if (provider.maskedByStoppedPackage) {
Intent intent = createUpdateIntentLocked(provider,
new int[] { widget.appWidgetId });
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getBroadcast(mContext, widget.appWidgetId,
+ PendingIntent.getBroadcastAsUser(mContext, widget.appWidgetId,
intent, PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ | PendingIntent.FLAG_IMMUTABLE, hostUser));
} else if (onClickIntent != null) {
views.setOnClickPendingIntent(android.R.id.background,
- PendingIntent.getActivity(mContext, widget.appWidgetId, onClickIntent,
- PendingIntent.FLAG_UPDATE_CURRENT
- | PendingIntent.FLAG_IMMUTABLE));
+ PendingIntent.getActivityAsUser(mContext, widget.appWidgetId,
+ onClickIntent, PendingIntent.FLAG_UPDATE_CURRENT
+ | PendingIntent.FLAG_IMMUTABLE, null /* options */,
+ hostUser));
}
if (widget.replaceWithMaskedViewsLocked(views)) {
scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked());
@@ -3246,6 +3281,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
deleteWidgetsLocked(provider, UserHandle.USER_ALL);
mProviders.remove(provider);
mGeneratedPreviewsApiCounter.remove(provider.id);
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ }
// no need to send the DISABLE broadcast, since the receiver is gone anyway
cancelBroadcastsLocked(provider);
@@ -3824,6 +3862,14 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
} catch (IOException e) {
Slog.w(TAG, "Failed to read state: " + e);
}
+
+ if (remoteViewsProto()) {
+ try {
+ loadGeneratedPreviewCategoriesLocked(profileId);
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to read preview categories: " + e);
+ }
+ }
}
if (version >= 0) {
@@ -4593,6 +4639,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
keep.add(providerId);
// Use the new AppWidgetProviderInfo.
provider.setPartialInfoLocked(info);
+ // Clear old previews
+ if (remoteViewsProto()) {
+ clearGeneratedPreviewsAsync(provider);
+ } else {
+ provider.clearGeneratedPreviewsLocked();
+ }
// If it's enabled
final int M = provider.widgets.size();
if (M > 0) {
@@ -4884,6 +4936,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mSecurityPolicy.enforceCallFromPackage(callingPackage);
ensureWidgetCategoryCombinationIsValid(widgetCategory);
+ AndroidFuture<RemoteViews> result = null;
synchronized (mLock) {
ensureGroupStateLoadedLocked(profileId);
final int providerCount = mProviders.size();
@@ -4917,10 +4970,23 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
callingPackage);
if (providerIsInCallerProfile && !shouldFilterAppAccess
&& (providerIsInCallerPackage || hasBindAppWidgetPermission)) {
- return provider.getGeneratedPreviewLocked(widgetCategory);
+ if (remoteViewsProto()) {
+ result = getGeneratedPreviewsAsync(provider, widgetCategory);
+ } else {
+ return provider.getGeneratedPreviewLocked(widgetCategory);
+ }
}
}
}
+
+ if (result != null) {
+ try {
+ return result.get();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to get generated previews Future result", e);
+ return null;
+ }
+ }
// Either the provider does not exist or the caller does not have permission to access its
// previews.
return null;
@@ -4950,8 +5016,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
providerComponent + " is not a valid AppWidget provider");
}
if (mGeneratedPreviewsApiCounter.tryApiCall(providerId)) {
- provider.setGeneratedPreviewLocked(widgetCategories, preview);
- scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ if (remoteViewsProto()) {
+ setGeneratedPreviewsAsync(provider, widgetCategories, preview);
+ } else {
+ provider.setGeneratedPreviewLocked(widgetCategories, preview);
+ scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
return true;
}
return false;
@@ -4979,11 +5049,361 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
throw new IllegalArgumentException(
providerComponent + " is not a valid AppWidget provider");
}
- final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
- if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+
+ if (remoteViewsProto()) {
+ removeGeneratedPreviewsAsync(provider, widgetCategories);
+ } else {
+ final boolean changed = provider.removeGeneratedPreviewLocked(widgetCategories);
+ if (changed) scheduleNotifyGroupHostsForProvidersChangedLocked(userId);
+ }
}
}
+ /**
+ * Return previews for the specified provider from a background thread. The result of the future
+ * is nullable.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private AndroidFuture<RemoteViews> getGeneratedPreviewsAsync(
+ @NonNull Provider provider, @AppWidgetProviderInfo.CategoryFlags int widgetCategory) {
+ AndroidFuture<RemoteViews> result = new AndroidFuture<>();
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int i = 0; i < previews.size(); i++) {
+ if ((widgetCategory & previews.keyAt(i)) != 0) {
+ result.complete(previews.valueAt(i));
+ return;
+ }
+ }
+ result.complete(null);
+ });
+ return result;
+ }
+
+ /**
+ * Set previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void setGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories,
+ @NonNull RemoteViews preview) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ previews.put(flag, preview);
+ }
+ }
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ });
+ }
+
+ /**
+ * Remove previews for the specified provider on a background thread.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void removeGeneratedPreviewsAsync(@NonNull Provider provider, int widgetCategories) {
+ mSavePreviewsHandler.post(() -> {
+ SparseArray<RemoteViews> previews = loadGeneratedPreviews(provider);
+ boolean changed = false;
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ changed |= previews.removeReturnOld(flag) != null;
+ }
+ }
+ if (changed) {
+ saveGeneratedPreviews(provider, previews, /* notify= */ true);
+ }
+ });
+ }
+
+ /**
+ * Clear previews for the specified provider on a background thread. Returns true if changed
+ * (i.e. there are previews to clear). If returns true, the caller should schedule a providers
+ * changed notification.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private boolean clearGeneratedPreviewsAsync(@NonNull Provider provider) {
+ mSavePreviewsHandler.post(() -> {
+ saveGeneratedPreviews(provider, /* previews= */ null, /* notify= */ false);
+ });
+ return provider.info.generatedPreviewCategories != 0;
+ }
+
+ private void checkSavePreviewsThread() {
+ if (DEBUG && !mSavePreviewsHandler.getLooper().isCurrentThread()) {
+ throw new IllegalStateException("Only modify previews on the background thread");
+ }
+ }
+
+ /**
+ * Load previews from file for the given provider. If there are no previews, returns an empty
+ * SparseArray. Else, returns a SparseArray of the previews mapped by widget category.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> loadGeneratedPreviews(@NonNull Provider provider) {
+ checkSavePreviewsThread();
+ try {
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ return new SparseArray<>();
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ SparseArray<RemoteViews> entries = readGeneratedPreviewsFromProto(input);
+ SparseArray<RemoteViews> singleCategoryKeyedEntries = new SparseArray<>();
+ for (int i = 0; i < entries.size(); i++) {
+ int widgetCategories = entries.keyAt(i);
+ RemoteViews preview = entries.valueAt(i);
+ for (int flag : Provider.WIDGET_CATEGORY_FLAGS) {
+ if ((widgetCategories & flag) != 0) {
+ singleCategoryKeyedEntries.put(flag, preview);
+ }
+ }
+ }
+ return singleCategoryKeyedEntries;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to load generated previews for " + provider, e);
+ return new SparseArray<>();
+ }
+ }
+
+ /**
+ * This is called when loading profile/group state to populate
+ * AppWidgetProviderInfo.generatedPreviewCategories based on what previews are saved.
+ *
+ * This is the only time previews are read while not on mSavePreviewsHandler. It happens once
+ * per profile during initialization, before any calls to get/set/removeWidgetPreviewAsync
+ * happen for that profile.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @GuardedBy("mLock")
+ private void loadGeneratedPreviewCategoriesLocked(int profileId) throws IOException {
+ for (Provider provider : mProviders) {
+ if (provider.id.getProfile().getIdentifier() != profileId) {
+ continue;
+ }
+ AtomicFile previewsFile = getWidgetPreviewsFile(provider);
+ if (!previewsFile.exists()) {
+ continue;
+ }
+ ProtoInputStream input = new ProtoInputStream(previewsFile.readFully());
+ provider.info.generatedPreviewCategories = readGeneratedPreviewCategoriesFromProto(
+ input);
+ if (DEBUG) {
+ Slog.i(TAG, TextUtils.formatSimple(
+ "loadGeneratedPreviewCategoriesLocked %d %s categories %d", profileId,
+ provider, provider.info.generatedPreviewCategories));
+ }
+ }
+ }
+
+ /**
+ * Save the given previews into storage.
+ *
+ * @param provider Provider for which to save previews
+ * @param previews Previews to save. If null or empty, clears any saved previews for this
+ * provider.
+ * @param notify If true, then this function will notify hosts of updated provider info.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void saveGeneratedPreviews(@NonNull Provider provider,
+ @Nullable SparseArray<RemoteViews> previews, boolean notify) {
+ checkSavePreviewsThread();
+ AtomicFile file = null;
+ FileOutputStream stream = null;
+ try {
+ file = getWidgetPreviewsFile(provider);
+ if (previews == null || previews.size() == 0) {
+ if (file.exists()) {
+ if (DEBUG) {
+ Slog.i(TAG, "Deleting widget preview file " + file);
+ }
+ file.delete();
+ }
+ } else {
+ if (DEBUG) {
+ Slog.i(TAG, "Writing widget preview file " + file);
+ }
+ ProtoOutputStream out = new ProtoOutputStream();
+ writePreviewsToProto(out, previews);
+ stream = file.startWrite();
+ stream.write(out.getBytes());
+ file.finishWrite(stream);
+ }
+
+ synchronized (mLock) {
+ provider.updateGeneratedPreviewCategoriesLocked(previews);
+ if (notify) {
+ scheduleNotifyGroupHostsForProvidersChangedLocked(provider.getUserId());
+ }
+ }
+ } catch (IOException e) {
+ if (file != null && stream != null) {
+ file.failWrite(stream);
+ }
+ Slog.w(TAG, "Failed to save widget previews for provider " + provider.id.componentName);
+ }
+ }
+
+
+ /**
+ * Write the given previews as a GeneratedPreviewsProto to the output stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ private void writePreviewsToProto(@NonNull ProtoOutputStream out,
+ @NonNull SparseArray<RemoteViews> generatedPreviews) {
+ // Collect RemoteViews mapped by hashCode in order to avoid writing duplicates.
+ SparseArray<Pair<Integer, RemoteViews>> previewsToWrite = new SparseArray<>();
+ for (int i = 0; i < generatedPreviews.size(); i++) {
+ int widgetCategory = generatedPreviews.keyAt(i);
+ RemoteViews views = generatedPreviews.valueAt(i);
+ if (!previewsToWrite.contains(views.hashCode())) {
+ previewsToWrite.put(views.hashCode(), new Pair<>(widgetCategory, views));
+ } else {
+ Pair<Integer, RemoteViews> entry = previewsToWrite.get(views.hashCode());
+ previewsToWrite.put(views.hashCode(),
+ Pair.create(entry.first | widgetCategory, views));
+ }
+ }
+
+ for (int i = 0; i < previewsToWrite.size(); i++) {
+ final long token = out.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = previewsToWrite.valueAt(i);
+ out.write(GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES, entry.first);
+ final long viewsToken = out.start(GeneratedPreviewsProto.Preview.VIEWS);
+ entry.second.writePreviewToProto(mContext, out);
+ out.end(viewsToken);
+ out.end(token);
+ }
+ }
+
+ /**
+ * Read a GeneratedPreviewsProto message from the input stream.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private SparseArray<RemoteViews> readGeneratedPreviewsFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ SparseArray<RemoteViews> entries = new SparseArray<>();
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ false);
+ entries.put(entry.first, entry.second);
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return entries;
+ }
+
+ /**
+ * Read the widget categories from GeneratedPreviewsProto and return an int representing the
+ * combined widget categories of all the previews.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @AppWidgetProviderInfo.CategoryFlags
+ private int readGeneratedPreviewCategoriesFromProto(@NonNull ProtoInputStream input)
+ throws IOException {
+ int widgetCategories = 0;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.PREVIEWS:
+ final long token = input.start(GeneratedPreviewsProto.PREVIEWS);
+ Pair<Integer, RemoteViews> entry = readSinglePreviewFromProto(input,
+ /* skipViews= */ true);
+ widgetCategories |= entry.first;
+ input.end(token);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return widgetCategories;
+ }
+
+ /**
+ * Read a single GeneratedPreviewsProto.Preview message from the input stream, and returns a
+ * pair of widget category and corresponding RemoteViews. If skipViews is true, this function
+ * will only read widget categories and the returned RemoteViews will be null.
+ */
+ @FlaggedApi(android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO)
+ @NonNull
+ private Pair<Integer, RemoteViews> readSinglePreviewFromProto(@NonNull ProtoInputStream input,
+ boolean skipViews) throws IOException {
+ int widgetCategories = 0;
+ RemoteViews views = null;
+ while (input.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (input.getFieldNumber()) {
+ case (int) GeneratedPreviewsProto.Preview.VIEWS:
+ if (skipViews) {
+ // ProtoInputStream will skip over the nested message when nextField() is
+ // called.
+ continue;
+ }
+ final long token = input.start(GeneratedPreviewsProto.Preview.VIEWS);
+ try {
+ views = RemoteViews.createPreviewFromProto(mContext, input);
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to deserialize RemoteViews", e);
+ }
+ input.end(token);
+ break;
+ case (int) GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES:
+ widgetCategories = input.readInt(
+ GeneratedPreviewsProto.Preview.WIDGET_CATEGORIES);
+ break;
+ default:
+ Slog.w(TAG, "Unknown field while reading GeneratedPreviewsProto! "
+ + ProtoUtils.currentFieldToString(input));
+ }
+ }
+ return Pair.create(widgetCategories, views);
+ }
+
+ /**
+ * Returns the file in which all generated previews for this provider are stored. This will be
+ * a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews/<package>-<class>-<uid>.binpb}
+ *
+ * This function will not create the file if it does not already exist.
+ */
+ @NonNull
+ private static AtomicFile getWidgetPreviewsFile(@NonNull Provider provider) throws IOException {
+ int userId = provider.getUserId();
+ File previewsDirectory = getWidgetPreviewsDirectory(userId);
+ File providerPreviews = Environment.buildPath(previewsDirectory,
+ TextUtils.formatSimple("%s-%s-%d.binpb", provider.id.componentName.getPackageName(),
+ provider.id.componentName.getClassName(), provider.id.uid));
+ return new AtomicFile(providerPreviews);
+ }
+
+ /**
+ * Returns the widget previews directory for the given user, creating it if it does not exist.
+ * This will be a path of the form:
+ * {@literal /data/system_ce/<userId>/appwidget/previews}
+ */
+ @NonNull
+ private static File getWidgetPreviewsDirectory(int userId) throws IOException {
+ File dataSystemCeDirectory = Environment.getDataSystemCeDirectory(userId);
+ File previewsDirectory = Environment.buildPath(dataSystemCeDirectory,
+ APPWIDGET_CE_DATA_DIRNAME, WIDGET_PREVIEWS_DIRNAME);
+ if (!previewsDirectory.exists()) {
+ if (!previewsDirectory.mkdirs()) {
+ throw new IOException("Unable to create widget preview directory "
+ + previewsDirectory.getPath());
+ }
+ }
+ return previewsDirectory;
+ }
+
private static void ensureWidgetCategoryCombinationIsValid(int widgetCategories) {
int validCategories = AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
| AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD
@@ -5414,11 +5834,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
AppWidgetManager.META_DATA_APPWIDGET_PROVIDER);
}
if (newInfo != null) {
+ newInfo.generatedPreviewCategories = info.generatedPreviewCategories;
info = newInfo;
if (DEBUG) {
Objects.requireNonNull(info);
}
- updateGeneratedPreviewCategoriesLocked();
}
}
mInfoParsed = true;
@@ -5475,7 +5895,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
generatedPreviews.put(flag, preview);
}
}
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
@GuardedBy("this.mLock")
@@ -5487,7 +5907,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
}
}
if (changed) {
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
}
return changed;
}
@@ -5496,17 +5916,19 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
public boolean clearGeneratedPreviewsLocked() {
if (generatedPreviews.size() > 0) {
generatedPreviews.clear();
- updateGeneratedPreviewCategoriesLocked();
+ updateGeneratedPreviewCategoriesLocked(generatedPreviews);
return true;
}
return false;
}
-
@GuardedBy("this.mLock")
- private void updateGeneratedPreviewCategoriesLocked() {
+ private void updateGeneratedPreviewCategoriesLocked(
+ @Nullable SparseArray<RemoteViews> previews) {
info.generatedPreviewCategories = 0;
- for (int i = 0; i < generatedPreviews.size(); i++) {
- info.generatedPreviewCategories |= generatedPreviews.keyAt(i);
+ if (previews != null) {
+ for (int i = 0; i < previews.size(); i++) {
+ info.generatedPreviewCategories |= previews.keyAt(i);
+ }
}
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 51034d24df14..7cba9e0ccca8 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -31,16 +31,13 @@ import static android.os.UserHandle.getCallingUserId;
import static com.android.internal.util.CollectionUtils.any;
import static com.android.internal.util.Preconditions.checkState;
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
-import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
import static java.util.Objects.requireNonNull;
-import static java.util.concurrent.TimeUnit.MINUTES;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -69,31 +66,22 @@ import android.companion.datatransfer.PermissionSyncRequest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.MacAddress;
-import android.net.NetworkPolicyManager;
import android.os.Binder;
-import android.os.Environment;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.PowerExemptionManager;
import android.os.PowerManagerInternal;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.flags.Flags;
-import android.util.ArraySet;
import android.util.ExceptionUtils;
import android.util.Slog;
-import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
@@ -114,35 +102,25 @@ import com.android.server.companion.devicepresence.DevicePresenceProcessor;
import com.android.server.companion.devicepresence.ObservableUuid;
import com.android.server.companion.devicepresence.ObservableUuidStore;
import com.android.server.companion.transport.CompanionTransportManager;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
-import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.List;
-import java.util.Set;
@SuppressLint("LongLogTag")
public class CompanionDeviceManagerService extends SystemService {
private static final String TAG = "CDM_CompanionDeviceManagerService";
private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min
-
- private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
- private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
private static final int MAX_CN_LENGTH = 500;
- private final ActivityTaskManagerInternal mAtmInternal;
- private final ActivityManagerInternal mAmInternal;
- private final IAppOpsService mAppOpsManager;
- private final PowerExemptionManager mPowerExemptionManager;
- private final PackageManagerInternal mPackageManagerInternal;
-
private final AssociationStore mAssociationStore;
private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
private final ObservableUuidStore mObservableUuidStore;
+
+ private final CompanionExemptionProcessor mCompanionExemptionProcessor;
private final AssociationRequestsProcessor mAssociationRequestsProcessor;
private final SystemDataTransferProcessor mSystemDataTransferProcessor;
private final BackupRestoreProcessor mBackupRestoreProcessor;
@@ -156,12 +134,15 @@ public class CompanionDeviceManagerService extends SystemService {
super(context);
final ActivityManager activityManager = context.getSystemService(ActivityManager.class);
- mPowerExemptionManager = context.getSystemService(PowerExemptionManager.class);
- mAppOpsManager = IAppOpsService.Stub.asInterface(
- ServiceManager.getService(Context.APP_OPS_SERVICE));
- mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
- mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
- mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+ final PowerExemptionManager powerExemptionManager = context.getSystemService(
+ PowerExemptionManager.class);
+ final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+ final ActivityTaskManagerInternal atmInternal = LocalServices.getService(
+ ActivityTaskManagerInternal.class);
+ final ActivityManagerInternal amInternal = LocalServices.getService(
+ ActivityManagerInternal.class);
+ final PackageManagerInternal packageManagerInternal = LocalServices.getService(
+ PackageManagerInternal.class);
final UserManager userManager = context.getSystemService(UserManager.class);
final PowerManagerInternal powerManagerInternal = LocalServices.getService(
PowerManagerInternal.class);
@@ -173,25 +154,29 @@ public class CompanionDeviceManagerService extends SystemService {
// Init processors
mAssociationRequestsProcessor = new AssociationRequestsProcessor(context,
- mPackageManagerInternal, mAssociationStore);
- mBackupRestoreProcessor = new BackupRestoreProcessor(context, mPackageManagerInternal,
+ packageManagerInternal, mAssociationStore);
+ mBackupRestoreProcessor = new BackupRestoreProcessor(context, packageManagerInternal,
mAssociationStore, associationDiskStore, mSystemDataTransferRequestStore,
mAssociationRequestsProcessor);
mCompanionAppBinder = new CompanionAppBinder(context);
+ mCompanionExemptionProcessor = new CompanionExemptionProcessor(context,
+ powerExemptionManager, appOpsManager, packageManagerInternal, atmInternal,
+ amInternal, mAssociationStore);
+
mDevicePresenceProcessor = new DevicePresenceProcessor(context,
mCompanionAppBinder, userManager, mAssociationStore, mObservableUuidStore,
- powerManagerInternal);
+ powerManagerInternal, mCompanionExemptionProcessor);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mDisassociationProcessor = new DisassociationProcessor(context, activityManager,
- mAssociationStore, mPackageManagerInternal, mDevicePresenceProcessor,
+ mAssociationStore, packageManagerInternal, mDevicePresenceProcessor,
mCompanionAppBinder, mSystemDataTransferRequestStore, mTransportManager);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this,
- mPackageManagerInternal, mAssociationStore,
+ packageManagerInternal, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
// TODO(b/279663946): move context sync to a dedicated system service
@@ -202,7 +187,6 @@ public class CompanionDeviceManagerService extends SystemService {
public void onStart() {
// Init association stores
mAssociationStore.refreshCache();
- mAssociationStore.registerLocalListener(mAssociationStoreChangeListener);
// Init UUID store
mObservableUuidStore.getObservableUuidsForUser(getContext().getUserId());
@@ -240,11 +224,8 @@ public class CompanionDeviceManagerService extends SystemService {
if (associations.isEmpty()) return;
- updateAtm(userId, associations);
-
- BackgroundThread.getHandler().sendMessageDelayed(
- obtainMessage(CompanionDeviceManagerService::maybeGrantAutoRevokeExemptions, this),
- MINUTES.toMillis(10));
+ mCompanionExemptionProcessor.updateAtm(userId, associations);
+ mCompanionExemptionProcessor.updateAutoRevokeExemptions();
}
@Override
@@ -262,9 +243,12 @@ public class CompanionDeviceManagerService extends SystemService {
if (!associationsForPackage.isEmpty()) {
Slog.i(TAG, "Package removed or data cleared for user=[" + userId + "], package=["
+ packageName + "]. Cleaning up CDM data...");
- }
- for (AssociationInfo association : associationsForPackage) {
- mDisassociationProcessor.disassociate(association.getId());
+
+ for (AssociationInfo association : associationsForPackage) {
+ mDisassociationProcessor.disassociate(association.getId());
+ }
+
+ mCompanionAppBinder.onPackageChanged(userId);
}
// Clear observable UUIDs for the package.
@@ -273,19 +257,16 @@ public class CompanionDeviceManagerService extends SystemService {
for (ObservableUuid uuid : uuidsTobeObserved) {
mObservableUuidStore.removeObservableUuid(userId, uuid.getUuid(), packageName);
}
-
- mCompanionAppBinder.onPackagesChanged(userId);
}
private void onPackageModifiedInternal(@UserIdInt int userId, @NonNull String packageName) {
- final List<AssociationInfo> associationsForPackage =
+ final List<AssociationInfo> associations =
mAssociationStore.getAssociationsByPackage(userId, packageName);
- for (AssociationInfo association : associationsForPackage) {
- updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
- association.getPackageName());
- }
+ if (!associations.isEmpty()) {
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
- mCompanionAppBinder.onPackagesChanged(userId);
+ mCompanionAppBinder.onPackageChanged(userId);
+ }
}
private void onPackageAddedInternal(@UserIdInt int userId, @NonNull String packageName) {
@@ -765,130 +746,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
}
- /**
- * Update special access for the association's package
- */
- public void updateSpecialAccessPermissionForAssociatedPackage(int userId, String packageName) {
- final PackageInfo packageInfo =
- getPackageInfo(getContext(), userId, packageName);
-
- Binder.withCleanCallingIdentity(() -> updateSpecialAccessPermissionAsSystem(packageInfo));
- }
-
- private void updateSpecialAccessPermissionAsSystem(PackageInfo packageInfo) {
- if (packageInfo == null) {
- return;
- }
-
- if (containsEither(packageInfo.requestedPermissions,
- android.Manifest.permission.RUN_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)) {
- mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
- } else {
- try {
- mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
- } catch (UnsupportedOperationException e) {
- Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
- + " whitelist. It might due to the package is whitelisted by the system.");
- }
- }
-
- NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(getContext());
- try {
- if (containsEither(packageInfo.requestedPermissions,
- android.Manifest.permission.USE_DATA_IN_BACKGROUND,
- android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)) {
- networkPolicyManager.addUidPolicy(
- packageInfo.applicationInfo.uid,
- NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
- } else {
- networkPolicyManager.removeUidPolicy(
- packageInfo.applicationInfo.uid,
- NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
- }
- } catch (IllegalArgumentException e) {
- Slog.e(TAG, e.getMessage());
- }
-
- exemptFromAutoRevoke(packageInfo.packageName, packageInfo.applicationInfo.uid);
- }
-
- private void exemptFromAutoRevoke(String packageName, int uid) {
- try {
- mAppOpsManager.setMode(
- AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
- uid,
- packageName,
- AppOpsManager.MODE_IGNORED);
- } catch (RemoteException e) {
- Slog.w(TAG, "Error while granting auto revoke exemption for " + packageName, e);
- }
- }
-
- private void updateAtm(int userId, List<AssociationInfo> associations) {
- final Set<Integer> companionAppUids = new ArraySet<>();
- for (AssociationInfo association : associations) {
- final int uid = mPackageManagerInternal.getPackageUid(association.getPackageName(),
- 0, userId);
- if (uid >= 0) {
- companionAppUids.add(uid);
- }
- }
- if (mAtmInternal != null) {
- mAtmInternal.setCompanionAppUids(userId, companionAppUids);
- }
- if (mAmInternal != null) {
- // Make a copy of the set and send it to ActivityManager.
- mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
- }
- }
-
- private void maybeGrantAutoRevokeExemptions() {
- Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
-
- PackageManager pm = getContext().getPackageManager();
- for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
- SharedPreferences pref = getContext().getSharedPreferences(
- new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
- Context.MODE_PRIVATE);
- if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
- continue;
- }
-
- try {
- final List<AssociationInfo> associations =
- mAssociationStore.getActiveAssociationsByUser(userId);
- for (AssociationInfo a : associations) {
- try {
- int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
- exemptFromAutoRevoke(a.getPackageName(), uid);
- } catch (PackageManager.NameNotFoundException e) {
- Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
- }
- }
- } finally {
- pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
- }
- }
- }
-
- private final AssociationStore.OnChangeListener mAssociationStoreChangeListener =
- new AssociationStore.OnChangeListener() {
- @Override
- public void onAssociationChanged(int changeType, AssociationInfo association) {
- Slog.d(TAG, "onAssociationChanged changeType=[" + changeType
- + "], association=[" + association);
-
- final int userId = association.getUserId();
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getActiveAssociationsByUser(userId);
-
- updateAtm(userId, updatedAssociations);
- updateSpecialAccessPermissionForAssociatedPackage(association.getUserId(),
- association.getPackageName());
- }
- };
-
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public void onPackageRemoved(String packageName, int uid) {
@@ -911,10 +768,6 @@ public class CompanionDeviceManagerService extends SystemService {
}
};
- private static <T> boolean containsEither(T[] array, T a, T b) {
- return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
- }
-
private class LocalService implements CompanionDeviceManagerServiceInternal {
@Override
diff --git a/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
new file mode 100644
index 000000000000..4969ffbfa08a
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/CompanionExemptionProcessor.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion;
+
+import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.AppOpsManager.MODE_IGNORED;
+
+import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
+
+import android.annotation.SuppressLint;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.companion.AssociationInfo;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.net.NetworkPolicyManager;
+import android.os.Binder;
+import android.os.Environment;
+import android.os.PowerExemptionManager;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
+import com.android.server.companion.association.AssociationStore;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+@SuppressLint("LongLogTag")
+public class CompanionExemptionProcessor {
+
+ private static final String TAG = "CDM_CompanionExemptionProcessor";
+
+ private static final String PREF_FILE_NAME = "companion_device_preferences.xml";
+ private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done";
+
+ private final Context mContext;
+ private final PowerExemptionManager mPowerExemptionManager;
+ private final AppOpsManager mAppOpsManager;
+ private final PackageManagerInternal mPackageManager;
+ private final ActivityTaskManagerInternal mAtmInternal;
+ private final ActivityManagerInternal mAmInternal;
+ private final AssociationStore mAssociationStore;
+
+ public CompanionExemptionProcessor(Context context, PowerExemptionManager powerExemptionManager,
+ AppOpsManager appOpsManager, PackageManagerInternal packageManager,
+ ActivityTaskManagerInternal atmInternal, ActivityManagerInternal amInternal,
+ AssociationStore associationStore) {
+ mContext = context;
+ mPowerExemptionManager = powerExemptionManager;
+ mAppOpsManager = appOpsManager;
+ mPackageManager = packageManager;
+ mAtmInternal = atmInternal;
+ mAmInternal = amInternal;
+ mAssociationStore = associationStore;
+
+ mAssociationStore.registerLocalListener(new AssociationStore.OnChangeListener() {
+ @Override
+ public void onAssociationChanged(int changeType, AssociationInfo association) {
+ final int userId = association.getUserId();
+ final List<AssociationInfo> updatedAssociations =
+ mAssociationStore.getActiveAssociationsByUser(userId);
+
+ updateAtm(userId, updatedAssociations);
+ }
+ });
+ }
+
+ /**
+ * Update ActivityManager and ActivityTaskManager exemptions
+ */
+ public void updateAtm(int userId, List<AssociationInfo> associations) {
+ final Set<Integer> companionAppUids = new ArraySet<>();
+ for (AssociationInfo association : associations) {
+ int uid = mPackageManager.getPackageUid(association.getPackageName(), 0, userId);
+ if (uid >= 0) {
+ companionAppUids.add(uid);
+ }
+ }
+ if (mAtmInternal != null) {
+ mAtmInternal.setCompanionAppUids(userId, companionAppUids);
+ }
+ if (mAmInternal != null) {
+ // Make a copy of the set and send it to ActivityManager.
+ mAmInternal.setCompanionAppUids(userId, new ArraySet<>(companionAppUids));
+ }
+ }
+
+ /**
+ * Update special access for the association's package
+ */
+ public void exemptPackage(int userId, String packageName, boolean hasPresentDevices) {
+ Slog.i(TAG, "Exempting package [" + packageName + "]...");
+
+ final PackageInfo packageInfo = getPackageInfo(mContext, userId, packageName);
+
+ Binder.withCleanCallingIdentity(
+ () -> exemptPackageAsSystem(userId, packageInfo, hasPresentDevices));
+ }
+
+ @SuppressLint("MissingPermission")
+ private void exemptPackageAsSystem(int userId, PackageInfo packageInfo,
+ boolean hasPresentDevices) {
+ if (packageInfo == null) {
+ return;
+ }
+
+ // If the app has run-in-bg permission and present devices, add it to power saver allowlist.
+ if (containsEither(packageInfo.requestedPermissions,
+ android.Manifest.permission.RUN_IN_BACKGROUND,
+ android.Manifest.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND)
+ && hasPresentDevices) {
+ mPowerExemptionManager.addToPermanentAllowList(packageInfo.packageName);
+ } else {
+ try {
+ mPowerExemptionManager.removeFromPermanentAllowList(packageInfo.packageName);
+ } catch (UnsupportedOperationException e) {
+ Slog.w(TAG, packageInfo.packageName + " can't be removed from power save"
+ + " allowlist. It might be due to the package being allowlisted by the"
+ + " system.");
+ }
+ }
+
+ // If the app has run-in-bg permission and present device, allow metered network use.
+ NetworkPolicyManager networkPolicyManager = NetworkPolicyManager.from(mContext);
+ try {
+ if (containsEither(packageInfo.requestedPermissions,
+ android.Manifest.permission.USE_DATA_IN_BACKGROUND,
+ android.Manifest.permission.REQUEST_COMPANION_USE_DATA_IN_BACKGROUND)
+ && hasPresentDevices) {
+ networkPolicyManager.addUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ } else {
+ networkPolicyManager.removeUidPolicy(
+ packageInfo.applicationInfo.uid,
+ NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, e.getMessage());
+ }
+
+ updateAutoRevokeExemption(packageInfo.packageName, packageInfo.applicationInfo.uid,
+ !mAssociationStore.getActiveAssociationsByPackage(userId,
+ packageInfo.packageName).isEmpty());
+ }
+
+ /**
+ * Update auto revoke exemptions.
+ * If the app has any association, exempt it from permission auto revoke.
+ */
+ public void updateAutoRevokeExemptions() {
+ Slog.d(TAG, "maybeGrantAutoRevokeExemptions()");
+
+ PackageManager pm = mContext.getPackageManager();
+ for (int userId : LocalServices.getService(UserManagerInternal.class).getUserIds()) {
+ SharedPreferences pref = mContext.getSharedPreferences(
+ new File(Environment.getUserSystemDirectory(userId), PREF_FILE_NAME),
+ Context.MODE_PRIVATE);
+ if (pref.getBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, false)) {
+ continue;
+ }
+
+ try {
+ final List<AssociationInfo> associations =
+ mAssociationStore.getActiveAssociationsByUser(userId);
+ for (AssociationInfo a : associations) {
+ try {
+ int uid = pm.getPackageUidAsUser(a.getPackageName(), userId);
+ updateAutoRevokeExemption(a.getPackageName(), uid, true);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Unknown companion package: " + a.getPackageName(), e);
+ }
+ }
+ } finally {
+ pref.edit().putBoolean(PREF_KEY_AUTO_REVOKE_GRANTS_DONE, true).apply();
+ }
+ }
+ }
+
+ @SuppressLint("MissingPermission")
+ private void updateAutoRevokeExemption(String packageName, int uid, boolean hasAssociations) {
+ try {
+ mAppOpsManager.setMode(
+ AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
+ uid,
+ packageName,
+ hasAssociations ? MODE_IGNORED : MODE_ALLOWED);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error while granting auto revoke exemption for " + packageName, e);
+ }
+ }
+
+ private <T> boolean containsEither(T[] array, T a, T b) {
+ return ArrayUtils.contains(array, a) || ArrayUtils.contains(array, b);
+ }
+
+}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
index 60f46887fa5c..8307da5f7218 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java
@@ -95,7 +95,9 @@ public class CompanionAppBinder {
/**
* On package changed.
*/
- public void onPackagesChanged(@UserIdInt int userId) {
+ public void onPackageChanged(@UserIdInt int userId) {
+ // Note: To invalidate the user space for simplicity. We could alternatively manage each
+ // package, but that would easily cause errors if one case is mis-handled.
mCompanionServicesRegister.invalidate(userId);
}
@@ -299,12 +301,14 @@ public class CompanionAppBinder {
private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
@Override
- public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+ @NonNull
+ public synchronized Map<String, List<ComponentName>> forUser(
@UserIdInt int userId) {
return super.forUser(userId);
}
- synchronized @NonNull List<ComponentName> forPackage(
+ @NonNull
+ synchronized List<ComponentName> forPackage(
@UserIdInt int userId, @NonNull String packageName) {
return forUser(userId).getOrDefault(packageName, Collections.emptyList());
}
@@ -314,7 +318,8 @@ public class CompanionAppBinder {
}
@Override
- protected final @NonNull Map<String, List<ComponentName>> create(@UserIdInt int userId) {
+ @NonNull
+ protected final Map<String, List<ComponentName>> create(@UserIdInt int userId) {
return PackageUtils.getCompanionServicesForUser(mContext, userId);
}
}
diff --git a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
index a374d279af0b..7b4dd7df8be3 100644
--- a/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
+++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java
@@ -57,6 +57,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.CollectionUtils;
+import com.android.server.companion.CompanionExemptionProcessor;
import com.android.server.companion.association.AssociationStore;
import java.io.PrintWriter;
@@ -101,6 +102,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
private final PowerManagerInternal mPowerManagerInternal;
@NonNull
private final UserManager mUserManager;
+ @NonNull
+ private final CompanionExemptionProcessor mCompanionExemptionProcessor;
// NOTE: Same association may appear in more than one of the following sets at the same time.
// (E.g. self-managed devices that have MAC addresses, could be reported as present by their
@@ -111,7 +114,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull
private final Set<Integer> mNearbyBleDevices = new HashSet<>();
@NonNull
- private final Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
+ private final Set<Integer> mConnectedSelfManagedDevices = new HashSet<>();
@NonNull
private final Set<ParcelUuid> mConnectedUuidDevices = new HashSet<>();
@NonNull
@@ -146,7 +149,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
@NonNull UserManager userManager,
@NonNull AssociationStore associationStore,
@NonNull ObservableUuidStore observableUuidStore,
- @NonNull PowerManagerInternal powerManagerInternal) {
+ @NonNull PowerManagerInternal powerManagerInternal,
+ @NonNull CompanionExemptionProcessor companionExemptionProcessor) {
mContext = context;
mCompanionAppBinder = companionAppBinder;
mAssociationStore = associationStore;
@@ -156,6 +160,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mObservableUuidStore, this);
mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this);
mPowerManagerInternal = powerManagerInternal;
+ mCompanionExemptionProcessor = companionExemptionProcessor;
}
/** Initialize {@link DevicePresenceProcessor} */
@@ -404,7 +409,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* nearby (for "self-managed" associations).
*/
public boolean isDevicePresent(int associationId) {
- return mReportedSelfManagedDevices.contains(associationId)
+ return mConnectedSelfManagedDevices.contains(associationId)
|| mConnectedBtDevices.contains(associationId)
|| mNearbyBleDevices.contains(associationId)
|| mSimulated.contains(associationId);
@@ -451,7 +456,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* notifyDeviceAppeared()}
*/
public void onSelfManagedDeviceConnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_APPEARED);
}
@@ -467,7 +472,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* notifyDeviceDisappeared()}
*/
public void onSelfManagedDeviceDisconnected(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@@ -475,7 +480,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
* Marks a "self-managed" device as disconnected when binderDied.
*/
public void onSelfManagedDeviceReporterBinderDied(int associationId) {
- onDevicePresenceEvent(mReportedSelfManagedDevices,
+ onDevicePresenceEvent(mConnectedSelfManagedDevices,
associationId, EVENT_SELF_MANAGED_DISAPPEARED);
}
@@ -683,6 +688,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
if (association.shouldBindWhenPresent()) {
bindApplicationIfNeeded(userId, packageName, association.isSelfManaged());
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, true);
} else {
return;
}
@@ -715,6 +721,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
// Check if there are other devices associated to the app that are present.
if (!shouldBindPackage(userId, packageName)) {
mCompanionAppBinder.unbindCompanionApp(userId, packageName);
+ mCompanionExemptionProcessor.exemptPackage(userId, packageName, false);
}
break;
default:
@@ -940,7 +947,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
mConnectedBtDevices.remove(id);
mNearbyBleDevices.remove(id);
- mReportedSelfManagedDevices.remove(id);
+ mConnectedSelfManagedDevices.remove(id);
mSimulated.remove(id);
synchronized (mBtDisconnectedDevices) {
mBtDisconnectedDevices.remove(id);
@@ -1100,7 +1107,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
out.append("Companion Device Present: ");
if (mConnectedBtDevices.isEmpty()
&& mNearbyBleDevices.isEmpty()
- && mReportedSelfManagedDevices.isEmpty()) {
+ && mConnectedSelfManagedDevices.isEmpty()) {
out.append("<empty>\n");
return;
} else {
@@ -1130,11 +1137,11 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene
}
out.append(" Self-Reported Devices: ");
- if (mReportedSelfManagedDevices.isEmpty()) {
+ if (mConnectedSelfManagedDevices.isEmpty()) {
out.append("<empty>\n");
} else {
out.append("\n");
- for (int associationId : mReportedSelfManagedDevices) {
+ for (int associationId : mConnectedSelfManagedDevices) {
AssociationInfo a = mAssociationStore.getAssociationById(associationId);
out.append(" ").append(a.toShortString()).append('\n');
}
diff --git a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
index ef39846f4750..8a4b1faf2df9 100644
--- a/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
+++ b/services/companion/java/com/android/server/companion/virtual/CameraAccessController.java
@@ -59,14 +59,14 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
private int mObserverCount = 0;
@GuardedBy("mLock")
- private ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
+ private final ArrayMap<String, InjectionSessionData> mPackageToSessionData = new ArrayMap<>();
/**
* Mapping from camera ID to open camera app associations. Key is the camera id, value is the
* information of the app's uid and package name.
*/
@GuardedBy("mLock")
- private ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
+ private final ArrayMap<String, OpenCameraInfo> mAppsToBlockOnVirtualDevice = new ArrayMap<>();
static class InjectionSessionData {
public int appUid;
@@ -179,6 +179,15 @@ class CameraAccessController extends CameraManager.AvailabilityCallback implemen
Slog.w(TAG, "Unexpected close with observers remaining: " + mObserverCount);
}
}
+ // Clean up camera injection sessions (if any).
+ synchronized (mLock) {
+ for (InjectionSessionData sessionData : mPackageToSessionData.values()) {
+ for (CameraInjectionSession session : sessionData.cameraIdToSession.values()) {
+ session.close();
+ }
+ }
+ mPackageToSessionData.clear();
+ }
mCameraManager.unregisterAvailabilityCallback(this);
}
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 281a2ce0556b..8b5b93e96494 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -1465,28 +1465,28 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
public int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@NonNull IVirtualDisplayCallback callback) {
checkCallerIsDeviceOwner();
+
+ int displayId;
+ boolean showPointer;
+ boolean isTrustedDisplay;
GenericWindowPolicyController gwpc;
synchronized (mVirtualDeviceLock) {
gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
- }
- int displayId;
- displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
- this, gwpc, mOwnerPackageName);
- boolean isMirrorDisplay =
- mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
- gwpc.setDisplayId(displayId, isMirrorDisplay);
- boolean isTrustedDisplay =
- (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
- == Display.FLAG_TRUSTED;
- if (!isTrustedDisplay) {
- if (getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
- throw new SecurityException("All displays must be trusted for devices with custom"
- + "clipboard policy.");
+ displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig,
+ callback, this, gwpc, mOwnerPackageName);
+ boolean isMirrorDisplay =
+ mDisplayManagerInternal.getDisplayIdToMirror(displayId)
+ != Display.INVALID_DISPLAY;
+ gwpc.setDisplayId(displayId, isMirrorDisplay);
+ isTrustedDisplay =
+ (mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
+ == Display.FLAG_TRUSTED;
+ if (!isTrustedDisplay
+ && getDevicePolicy(POLICY_TYPE_CLIPBOARD) != DEVICE_POLICY_DEFAULT) {
+ throw new SecurityException("All displays must be trusted for devices with "
+ + "custom clipboard policy.");
}
- }
- boolean showPointer;
- synchronized (mVirtualDeviceLock) {
if (mVirtualDisplays.contains(displayId)) {
gwpc.unregisterRunningAppsChangedListener(this);
throw new IllegalStateException(
@@ -1523,6 +1523,9 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
}
private PowerManager.WakeLock createAndAcquireWakeLockForDisplay(int displayId) {
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()) {
+ return null;
+ }
final long token = Binder.clearCallingIdentity();
try {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
@@ -1681,6 +1684,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub
return mOwnerUid;
}
+ long getDimDurationMillis() {
+ return mParams.getDimDuration().toMillis();
+ }
+
+ long getScreenOffTimeoutMillis() {
+ return mParams.getScreenOffTimeout().toMillis();
+ }
+
@Override // Binder call
public int[] getDisplayIds() {
synchronized (mVirtualDeviceLock) {
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 f87e3c338df7..6729231d68ab 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -17,6 +17,7 @@
package com.android.server.companion.virtual;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
@@ -27,6 +28,7 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.app.ActivityOptions;
+import android.app.compat.CompatChanges;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
@@ -41,11 +43,14 @@ import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.flags.Flags;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtualnative.IVirtualDeviceManagerNative;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.AttributionSource;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManagerInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.LocaleList;
@@ -88,7 +93,6 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
-
@SuppressLint("LongLogTag")
public class VirtualDeviceManagerService extends SystemService {
@@ -101,6 +105,11 @@ public class VirtualDeviceManagerService extends SystemService {
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /** Enable default device camera access for apps running on virtual devices. */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ public static final long ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS = 371173368L;
+
/**
* A virtual device association id corresponding to no CDM association.
*/
@@ -110,7 +119,7 @@ public class VirtualDeviceManagerService extends SystemService {
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
private final VirtualDeviceManagerInternal mLocalService;
- private VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
+ private final VirtualDeviceLog mVirtualDeviceLog = new VirtualDeviceLog(getContext());
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final PendingTrampolineMap mPendingTrampolines = new PendingTrampolineMap(mHandler);
@@ -236,7 +245,7 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- void onCameraAccessBlocked(int appUid) {
+ private void onCameraAccessBlocked(int appUid) {
ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot();
for (int i = 0; i < virtualDevicesSnapshot.size(); i++) {
VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i);
@@ -248,8 +257,13 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
- CameraAccessController getCameraAccessController(UserHandle userHandle) {
- if (Flags.streamCamera()) {
+ private CameraAccessController getCameraAccessController(UserHandle userHandle,
+ VirtualDeviceParams params, String callingPackage) {
+ if (CompatChanges.isChangeEnabled(ENABLE_DEFAULT_DEVICE_CAMERA_ACCESS, callingPackage,
+ userHandle)
+ && android.companion.virtualdevice.flags.Flags.defaultDeviceCameraAccessPolicy()
+ && (params.getDevicePolicy(POLICY_TYPE_DEFAULT_DEVICE_CAMERA_ACCESS)
+ == DEVICE_POLICY_DEFAULT)) {
return null;
}
int userId = userHandle.getIdentifier();
@@ -496,7 +510,8 @@ public class VirtualDeviceManagerService extends SystemService {
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
- getCameraAccessController(userHandle);
+ getCameraAccessController(userHandle, params,
+ attributionSource.getPackageName());
final int deviceId = sNextUniqueIndex.getAndIncrement();
final Consumer<ArraySet<Integer>> runningAppsChangedCallback =
runningUids -> notifyRunningAppsChanged(deviceId, runningUids);
@@ -576,7 +591,6 @@ public class VirtualDeviceManagerService extends SystemService {
}
}
-
@Override // Binder call
public int getDeviceIdForDisplayId(int displayId) {
if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
@@ -911,6 +925,22 @@ public class VirtualDeviceManagerService extends SystemService {
}
@Override
+ public long getDimDurationMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getDimDurationMillis();
+ }
+ }
+
+ @Override
+ public long getScreenOffTimeoutMillisForDeviceId(int deviceId) {
+ synchronized (mVirtualDeviceManagerLock) {
+ VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+ return virtualDevice == null ? -1 : virtualDevice.getScreenOffTimeoutMillis();
+ }
+ }
+
+ @Override
public boolean isValidVirtualDeviceId(int deviceId) {
return mImpl.isValidVirtualDeviceId(deviceId);
}
diff --git a/services/core/java/com/android/server/SerialService.java b/services/core/java/com/android/server/SerialService.java
index dbf144f0c63e..95ae11e28218 100644
--- a/services/core/java/com/android/server/SerialService.java
+++ b/services/core/java/com/android/server/SerialService.java
@@ -18,22 +18,30 @@ package com.android.server;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.hardware.ISerialManager;
import android.hardware.SerialManagerInternal;
import android.os.ParcelFileDescriptor;
import android.os.PermissionEnforcer;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import java.io.File;
+import java.io.FileDescriptor;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.function.Supplier;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class SerialService extends ISerialManager.Stub {
+ private static final String TAG = "SerialService";
+
private final Context mContext;
@GuardedBy("mSerialPorts")
@@ -50,7 +58,7 @@ public class SerialService extends ISerialManager.Stub {
final String[] serialPorts = getSerialPorts(context);
for (String serialPort : serialPorts) {
mSerialPorts.put(serialPort, () -> {
- return native_open(serialPort);
+ return tryOpen(serialPort);
});
}
}
@@ -130,5 +138,14 @@ public class SerialService extends ISerialManager.Stub {
}
};
- private native ParcelFileDescriptor native_open(String path);
+ private static @Nullable ParcelFileDescriptor tryOpen(String path) {
+ try {
+ FileDescriptor fd = Os.open(path, OsConstants.O_RDWR | OsConstants.O_NOCTTY, 0);
+ return new ParcelFileDescriptor(fd);
+ } catch (ErrnoException e) {
+ Slog.e(TAG, "Could not open: " + path, e);
+ // We return null to preserve API semantics from earlier implementation variants.
+ return null;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/TradeInModeService.java b/services/core/java/com/android/server/TradeInModeService.java
index 9ad550b6caf9..70a033086261 100644
--- a/services/core/java/com/android/server/TradeInModeService.java
+++ b/services/core/java/com/android/server/TradeInModeService.java
@@ -110,6 +110,8 @@ public final class TradeInModeService extends SystemService {
stopTradeInMode();
} else {
watchForSetupCompletion();
+ watchForNetworkChange();
+ watchForAccountsCreated();
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1c3569dd52d0..3e7bcb81c47f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5155,6 +5155,11 @@ public class ActivityManagerService extends IActivityManager.Stub
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+
String[] pkgs = intent.getStringArrayExtra(Intent.EXTRA_PACKAGES);
if (pkgs != null) {
for (String pkg : pkgs) {
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index de3e2c90efe1..08632fe09b19 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -114,6 +114,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_MEDIUM_APP_ADJ;
import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -696,7 +697,7 @@ public class OomAdjuster {
// In case the app goes from non-cached to cached but it doesn't have other reachable
// processes, its adj could be still unknown as of now, assign one.
processes.add(app);
- assignCachedAdjIfNecessary(processes);
+ applyLruAdjust(processes);
applyOomAdjLSP(app, false, mInjector.getUptimeMillis(),
mInjector.getElapsedRealtimeMillis(), oomAdjReason);
}
@@ -1086,7 +1087,7 @@ public class OomAdjuster {
}
mProcessesInCycle.clear();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, activeUids, now, nowElapsed, oldTime, true);
@@ -1148,8 +1149,9 @@ public class OomAdjuster {
}
@GuardedBy({"mService", "mProcLock"})
- protected void assignCachedAdjIfNecessary(ArrayList<ProcessRecord> lruList) {
+ protected void applyLruAdjust(ArrayList<ProcessRecord> lruList) {
final int numLru = lruList.size();
+ int nextPreviousAppAdj = PREVIOUS_APP_ADJ;
if (mConstants.USE_TIERED_CACHED_ADJ) {
final long now = mInjector.getUptimeMillis();
int uiTargetAdj = 10;
@@ -1159,9 +1161,12 @@ public class OomAdjuster {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
final ProcessCachedOptimizerRecord opt = app.mOptRecord;
- if (!app.isKilledByAm() && app.getThread() != null
- && (state.getCurAdj() >= UNKNOWN_ADJ
- || (state.hasShownUi() && state.getCurAdj() >= CACHED_APP_MIN_ADJ))) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null && (curAdj >= UNKNOWN_ADJ
+ || (state.hasShownUi() && curAdj >= CACHED_APP_MIN_ADJ))) {
final ProcessServiceRecord psr = app.mServices;
int targetAdj = CACHED_APP_MIN_ADJ;
@@ -1228,10 +1233,13 @@ public class OomAdjuster {
for (int i = numLru - 1; i >= 0; i--) {
ProcessRecord app = lruList.get(i);
final ProcessStateRecord state = app.mState;
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (!app.isKilledByAm() && app.getThread() != null && state.getCurAdj()
- >= UNKNOWN_ADJ) {
+ final int curAdj = state.getCurAdj();
+ if (PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ) {
+ state.setCurAdj(nextPreviousAppAdj);
+ nextPreviousAppAdj = Math.min(nextPreviousAppAdj + 1, PREVIOUS_APP_MAX_ADJ);
+ } else if (!app.isKilledByAm() && app.getThread() != null
+ && curAdj >= UNKNOWN_ADJ) {
+ // If we haven't yet assigned the final cached adj to the process, do that now.
final ProcessServiceRecord psr = app.mServices;
switch (state.getCurProcState()) {
case PROCESS_STATE_LAST_ACTIVITY:
diff --git a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
index e452c45156c9..8b660559f550 100644
--- a/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
+++ b/services/core/java/com/android/server/am/OomAdjusterModernImpl.java
@@ -54,6 +54,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SERVICE_ADJ;
import static com.android.server.am.ProcessList.SERVICE_B_ADJ;
@@ -968,7 +969,7 @@ public class OomAdjusterModernImpl extends OomAdjuster {
mTmpOomAdjusterArgs.update(topApp, now, UNKNOWN_ADJ, oomAdjReason, null, true);
computeConnectionsLSP();
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
postUpdateOomAdjInnerLSP(oomAdjReason, mActiveUids, now, nowElapsed, oldTime, true);
}
@@ -1049,20 +1050,24 @@ public class OomAdjusterModernImpl extends OomAdjuster {
// Now traverse and compute the connections of processes with changed importance.
computeConnectionsLSP();
- boolean unassignedAdj = false;
+ boolean needLruAdjust = false;
for (int i = 0, size = reachables.size(); i < size; i++) {
final ProcessStateRecord state = reachables.get(i).mState;
state.setReachable(false);
state.setCompletedAdjSeq(mAdjSeq);
- if (state.getCurAdj() >= UNKNOWN_ADJ) {
- unassignedAdj = true;
+ final int curAdj = state.getCurAdj();
+ // Processes assigned the PREV oomscore will have a laddered oomscore with respect to
+ // their positions in the LRU list. i.e. prev+0, prev+1, prev+2, etc.
+ final boolean isPrevApp = PREVIOUS_APP_ADJ <= curAdj && curAdj <= PREVIOUS_APP_MAX_ADJ;
+ if (curAdj >= UNKNOWN_ADJ || (Flags.oomadjusterPrevLaddering() && isPrevApp)) {
+ needLruAdjust = true;
}
}
// If all processes have an assigned adj, no need to calculate and assign cached adjs.
- if (unassignedAdj) {
+ if (needLruAdjust) {
// TODO: b/319163103 - optimize cache adj assignment to not require the whole lru list.
- assignCachedAdjIfNecessary(mProcessList.getLruProcessesLOSP());
+ applyLruAdjust(mProcessList.getLruProcessesLOSP());
}
// Repopulate any uid record that may have changed.
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index cdb01889c139..f86474f0dcaf 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -225,6 +225,7 @@ public final class ProcessList {
// UI flow such as clicking on a URI in the e-mail app to view in the browser,
// and then pressing back to return to e-mail.
public static final int PREVIOUS_APP_ADJ = 700;
+ public static final int PREVIOUS_APP_MAX_ADJ = Flags.oomadjusterPrevLaddering() ? 799 : 700;
// This is a process holding the home application -- we want to try
// avoiding killing it, even if it would normally be in the background,
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 56cfdfb7edde..7b4d6c7fff82 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -242,4 +242,12 @@ flag {
metadata {
purpose: PURPOSE_BUGFIX
}
+}
+
+flag {
+ name: "oomadjuster_prev_laddering"
+ namespace: "system_performance"
+ is_fixed_read_only: true
+ description: "Add +X to the prev scores according to their positions in the process LRU list"
+ bug: "359912586"
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 5db6dc7ccc15..6ccb3ee8bcc9 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -235,6 +235,9 @@ public class AppBindingService extends Binder {
}
final String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
if (Intent.ACTION_USER_REMOVED.equals(action)) {
onUserRemoved(userId);
diff --git a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
index ae93991d3945..0855815b67a9 100644
--- a/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsRestrictionsImpl.java
@@ -65,27 +65,31 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
@Override
public boolean setGlobalRestriction(Object clientToken, int code, boolean restricted) {
+ boolean changed;
if (restricted) {
if (!mGlobalRestrictions.containsKey(clientToken)) {
mGlobalRestrictions.put(clientToken, new SparseBooleanArray());
}
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
Objects.requireNonNull(restrictedCodes);
- boolean changed = !restrictedCodes.get(code);
+ changed = !restrictedCodes.get(code);
restrictedCodes.put(code, true);
- return changed;
} else {
SparseBooleanArray restrictedCodes = mGlobalRestrictions.get(clientToken);
if (restrictedCodes == null) {
return false;
}
- boolean changed = restrictedCodes.get(code);
+ changed = restrictedCodes.get(code);
restrictedCodes.delete(code);
if (restrictedCodes.size() == 0) {
mGlobalRestrictions.remove(clientToken);
}
- return changed;
}
+
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
+ return changed;
}
@Override
@@ -104,7 +108,11 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
@Override
public boolean clearGlobalRestrictions(Object clientToken) {
- return mGlobalRestrictions.remove(clientToken) != null;
+ boolean changed = mGlobalRestrictions.remove(clientToken) != null;
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
+ return changed;
}
@RequiresPermission(anyOf = {
@@ -122,6 +130,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
changed |= putUserRestrictionExclusions(clientToken, userIds[i],
excludedPackageTags);
}
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
@@ -191,6 +202,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
changed |= mUserRestrictions.remove(clientToken) != null;
changed |= mUserRestrictionExcludedPackageTags.remove(clientToken) != null;
notifyAllUserRestrictions(allUserRestrictedCodes);
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
@@ -244,6 +258,9 @@ public class AppOpsRestrictionsImpl implements AppOpsRestrictions {
}
}
+ if (changed) {
+ AppOpsManager.invalidateAppOpModeCache();
+ }
return changed;
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 702ad9541af8..5e74d67905a6 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -998,6 +998,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onUidModeChanged(int uid, int code, int mode,
String persistentDeviceId) {
+ AppOpsManager.invalidateAppOpModeCache();
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForAllPkgsInUid, AppOpsService.this,
code, uid, false, persistentDeviceId));
@@ -1006,6 +1007,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onPackageModeChanged(String packageName, int userId, int code,
int mode) {
+ AppOpsManager.invalidateAppOpModeCache();
mHandler.sendMessage(PooledLambda.obtainMessage(
AppOpsService::notifyOpChangedForPkg, AppOpsService.this,
packageName, code, mode, userId));
@@ -1032,6 +1034,11 @@ public class AppOpsService extends IAppOpsService.Stub {
// To migrate storageFile to recentAccessesFile, these reads must be called in this order.
readRecentAccesses();
mAppOpsCheckingService.readState();
+ // The system property used by the cache is created the first time it is written, that only
+ // happens inside invalidateCache(). Until the service calls invalidateCache() the property
+ // will not exist and the nonce will be UNSET.
+ AppOpsManager.invalidateAppOpModeCache();
+ AppOpsManager.disableAppOpModeCache();
}
public void publish() {
@@ -2830,6 +2837,13 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public int checkOperationRaw(int code, int uid, String packageName,
@Nullable String attributionTag) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
Context.DEVICE_ID_DEFAULT, true /*raw*/);
}
@@ -2837,6 +2851,13 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public int checkOperationRawForDevice(int code, int uid, @Nullable String packageName,
@Nullable String attributionTag, int virtualDeviceId) {
+ if (Binder.getCallingPid() != Process.myPid()
+ && Flags.appopAccessTrackingLoggingEnabled()) {
+ FrameworkStatsLog.write(
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED, uid, code,
+ APP_OP_NOTE_OP_OR_CHECK_OP_BINDER_API_CALLED__BINDER_API__CHECK_OPERATION,
+ false);
+ }
return mCheckOpsDelegateDispatcher.checkOperation(code, uid, packageName, attributionTag,
virtualDeviceId, true /*raw*/);
}
@@ -2894,8 +2915,14 @@ public class AppOpsService extends IAppOpsService.Stub {
return AppOpsManager.MODE_IGNORED;
}
}
- return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
- virtualDeviceId, raw);
+
+ if (Flags.appopModeCachingEnabled()) {
+ return getAppOpMode(code, uid, resolvedPackageName, attributionTag, virtualDeviceId,
+ raw, true);
+ } else {
+ return checkOperationUnchecked(code, uid, resolvedPackageName, attributionTag,
+ virtualDeviceId, raw);
+ }
}
/**
@@ -2961,6 +2988,54 @@ public class AppOpsService extends IAppOpsService.Stub {
}
}
+ /**
+ * This method unifies mode checking logic between checkOperationUnchecked and
+ * noteOperationUnchecked. It can replace those two methods once the flag is fully rolled out.
+ *
+ * @param isCheckOp This param is only used in user's op restriction. When checking if a package
+ * can bypass user's restriction we should account for attributionTag as well.
+ * But existing checkOp APIs don't accept attributionTag so we added a hack to
+ * skip attributionTag check for checkOp. After we add an overload of checkOp
+ * that accepts attributionTag we should remove this param.
+ */
+ private @Mode int getAppOpMode(int code, int uid, @NonNull String packageName,
+ @Nullable String attributionTag, int virtualDeviceId, boolean raw, boolean isCheckOp) {
+ PackageVerificationResult pvr;
+ try {
+ pvr = verifyAndGetBypass(uid, packageName, attributionTag);
+ } catch (SecurityException e) {
+ logVerifyAndGetBypassFailure(uid, e, "getAppOpMode");
+ return MODE_IGNORED;
+ }
+
+ if (isOpRestrictedDueToSuspend(code, packageName, uid)) {
+ return MODE_IGNORED;
+ }
+
+ synchronized (this) {
+ if (isOpRestrictedLocked(uid, code, packageName, attributionTag, virtualDeviceId,
+ pvr.bypass, isCheckOp)) {
+ return MODE_IGNORED;
+ }
+ if (isOpAllowedForUid(uid)) {
+ return MODE_ALLOWED;
+ }
+
+ int switchCode = AppOpsManager.opToSwitch(code);
+ int rawUidMode = mAppOpsCheckingService.getUidMode(uid,
+ getPersistentId(virtualDeviceId), switchCode);
+
+ if (rawUidMode != AppOpsManager.opToDefaultMode(switchCode)) {
+ return raw ? rawUidMode : evaluateForegroundMode(uid, switchCode, rawUidMode);
+ }
+
+ int rawPackageMode = mAppOpsCheckingService.getPackageMode(packageName, switchCode,
+ UserHandle.getUserId(uid));
+ return raw ? rawPackageMode : evaluateForegroundMode(uid, switchCode, rawPackageMode);
+ }
+ }
+
+
@Override
public int checkAudioOperation(int code, int usage, int uid, String packageName) {
return mCheckOpsDelegateDispatcher.checkAudioOperation(code, usage, uid, packageName);
@@ -3213,7 +3288,6 @@ public class AppOpsService extends IAppOpsService.Stub {
PackageVerificationResult pvr;
try {
pvr = verifyAndGetBypass(uid, packageName, attributionTag, proxyPackageName);
- boolean wasNull = attributionTag == null;
if (!pvr.isAttributionTagValid) {
attributionTag = null;
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 985155d0d891..0cf55bb2bf17 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -493,7 +493,7 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_INIT_ADI_DEVICE_STATES = 103;
private static final int MSG_INIT_INPUT_GAINS = 104;
- private static final int MSG_SET_INPUT_GAIN_INDEX = 105;
+ private static final int MSG_APPLY_INPUT_GAIN_INDEX = 105;
private static final int MSG_PERSIST_INPUT_GAIN_INDEX = 106;
// end of messages handled under wakelock
@@ -1626,7 +1626,6 @@ public class AudioService extends IAudioService.Stub
new InputDeviceVolumeHelper(
mSettings,
mContentResolver,
- mSettingsLock,
System.INPUT_GAIN_INDEX_SETTINGS);
}
@@ -5804,7 +5803,7 @@ public class AudioService extends IAudioService.Stub
// to persist).
sendMsg(
mAudioHandler,
- MSG_SET_INPUT_GAIN_INDEX,
+ MSG_APPLY_INPUT_GAIN_INDEX,
SENDMSG_QUEUE,
/*arg1*/ index,
/*arg2*/ 0,
@@ -5813,22 +5812,22 @@ public class AudioService extends IAudioService.Stub
}
}
- private void setInputGainIndexInt(@NonNull AudioDeviceAttributes ada, int index) {
+ private void onApplyInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
// TODO(b/364923030): call AudioSystem to apply input gain in native layer.
// Post a persist input gain msg.
sendMsg(
mAudioHandler,
MSG_PERSIST_INPUT_GAIN_INDEX,
- SENDMSG_QUEUE,
- /*arg1*/ index,
+ SENDMSG_REPLACE,
+ /*arg1*/ 0,
/*arg2*/ 0,
/*obj*/ ada,
PERSIST_DELAY);
}
- private void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
- mInputDeviceVolumeHelper.persistInputGainIndex(ada, index);
+ private void onPersistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
+ mInputDeviceVolumeHelper.persistInputGainIndex(ada);
}
/**
@@ -10213,12 +10212,12 @@ public class AudioService extends IAudioService.Stub
vgs.persistVolumeGroup(msg.arg1);
break;
- case MSG_SET_INPUT_GAIN_INDEX:
- setInputGainIndexInt((AudioDeviceAttributes) msg.obj, msg.arg1);
+ case MSG_APPLY_INPUT_GAIN_INDEX:
+ onApplyInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
break;
case MSG_PERSIST_INPUT_GAIN_INDEX:
- persistInputGainIndex((AudioDeviceAttributes) msg.obj, msg.arg1);
+ onPersistInputGainIndex((AudioDeviceAttributes) msg.obj);
break;
case MSG_PERSIST_RINGER_MODE:
diff --git a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
index d83dca629d74..d094629cc57b 100644
--- a/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
+++ b/services/core/java/com/android/server/audio/InputDeviceVolumeHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -43,7 +43,6 @@ import java.util.Set;
private final SettingsAdapter mSettings;
private final ContentResolver mContentResolver;
- private final Object mSettingsLock;
private final String mInputGainIndexSettingsName;
// A map between device internal type (e.g. AudioSystem.DEVICE_IN_BUILTIN_MIC) to its input gain
@@ -54,11 +53,9 @@ import java.util.Set;
InputDeviceVolumeHelper(
SettingsAdapter settings,
ContentResolver contentResolver,
- Object settingsLock,
String settingsName) {
mSettings = settings;
mContentResolver = contentResolver;
- mSettingsLock = settingsLock;
mInputGainIndexSettingsName = settingsName;
IntArray internalDeviceTypes = new IntArray();
@@ -82,34 +79,27 @@ import java.util.Set;
readSettings();
}
- public void readSettings() {
+ private void readSettings() {
synchronized (InputDeviceVolumeHelper.class) {
for (int inputDeviceType : mSupportedDeviceTypes) {
// Retrieve current input gain for device. If no input gain stored for current
// device, use default input gain.
- int index;
- if (!hasValidSettingsName()) {
- index = INDEX_DEFAULT;
- } else {
- String name = getSettingNameForDevice(inputDeviceType);
- index =
- mSettings.getSystemIntForUser(
- mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
- }
+ String name = getSettingNameForDevice(inputDeviceType);
+ int index = name == null
+ ? INDEX_DEFAULT
+ : mSettings.getSystemIntForUser(
+ mContentResolver, name, INDEX_DEFAULT, UserHandle.USER_CURRENT);
mInputGainIndexMap.put(inputDeviceType, getValidIndex(index));
}
}
}
- public boolean hasValidSettingsName() {
- return mInputGainIndexSettingsName != null && !mInputGainIndexSettingsName.isEmpty();
- }
-
- public @Nullable String getSettingNameForDevice(int inputDeviceType) {
- if (!hasValidSettingsName()) {
+ private @Nullable String getSettingNameForDevice(int inputDeviceType) {
+ if (mInputGainIndexSettingsName == null || mInputGainIndexSettingsName.isEmpty()) {
return null;
}
+
final String suffix = AudioSystem.getInputDeviceName(inputDeviceType);
if (suffix.isEmpty()) {
return mInputGainIndexSettingsName;
@@ -158,29 +148,27 @@ import java.util.Set;
ensureValidInputDeviceType(inputDeviceType);
int oldIndex;
- synchronized (mSettingsLock) {
- synchronized (InputDeviceVolumeHelper.class) {
- oldIndex = getInputGainIndex(ada);
- index = getValidIndex(index);
-
- if (oldIndex == index) {
- return false;
- }
+ synchronized (InputDeviceVolumeHelper.class) {
+ oldIndex = getInputGainIndex(ada);
+ index = getValidIndex(index);
- mInputGainIndexMap.put(inputDeviceType, index);
- return true;
+ if (oldIndex == index) {
+ return false;
}
+
+ mInputGainIndexMap.put(inputDeviceType, index);
+ return true;
}
}
- public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada, int index) {
+ public void persistInputGainIndex(@NonNull AudioDeviceAttributes ada) {
int inputDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalInputDevice(ada.getType());
- ensureValidInputDeviceType(inputDeviceType);
-
- if (hasValidSettingsName()) {
+ String name = getSettingNameForDevice(inputDeviceType);
+ if (name != null) {
+ int index = getInputGainIndex(ada);
mSettings.putSystemIntForUser(
mContentResolver,
- getSettingNameForDevice(inputDeviceType),
+ name,
index,
UserHandle.USER_CURRENT);
}
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index 6e38733f04c2..471b7b4ddfc8 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -167,6 +167,12 @@ public abstract class VirtualDeviceManagerInternal {
*/
public abstract int getDeviceIdForDisplayId(int displayId);
+ /** Returns the dim duration for the displays of the device with the given ID. */
+ public abstract long getDimDurationMillisForDeviceId(int deviceId);
+
+ /** Returns the screen off timeout of the displays of the device with the given ID. */
+ public abstract long getScreenOffTimeoutMillisForDeviceId(int deviceId);
+
/**
* Gets the persistent ID for the VirtualDevice with the given device ID.
*
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 421145390190..dabef84fec31 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -51,6 +51,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.ArrayMap;
@@ -64,6 +65,7 @@ import android.view.SurfaceControl;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.feature.DisplayManagerFlags;
import java.io.PrintWriter;
@@ -323,7 +325,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private int mWidth;
private int mHeight;
private int mDensityDpi;
- private float mRequestedRefreshRate;
+ private final float mRequestedRefreshRate;
private Surface mSurface;
private DisplayDeviceInfo mInfo;
private int mDisplayState;
@@ -332,7 +334,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private Display.Mode mMode;
private int mDisplayIdToMirror;
private boolean mIsWindowManagerMirroring;
- private DisplayCutout mDisplayCutout;
+ private final DisplayCutout mDisplayCutout;
+ private final float mDefaultBrightness;
+ private float mCurrentBrightness;
public VirtualDisplayDevice(IBinder displayToken, IBinder appToken,
int ownerUid, String ownerPackageName, Surface surface, int flags,
@@ -349,6 +353,8 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mDensityDpi = virtualDisplayConfig.getDensityDpi();
mRequestedRefreshRate = virtualDisplayConfig.getRequestedRefreshRate();
mDisplayCutout = virtualDisplayConfig.getDisplayCutout();
+ mDefaultBrightness = virtualDisplayConfig.getDefaultBrightness();
+ mCurrentBrightness = mDefaultBrightness;
mMode = createMode(mWidth, mHeight, getRefreshRate());
mSurface = surface;
mFlags = flags;
@@ -457,6 +463,12 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mCallback.dispatchDisplayResumed();
}
}
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && BrightnessUtils.isValidBrightnessValue(brightnessState)
+ && brightnessState != mCurrentBrightness) {
+ mCurrentBrightness = brightnessState;
+ mCallback.dispatchRequestedBrightnessChanged(mCurrentBrightness);
+ }
return null;
}
@@ -623,6 +635,10 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
mInfo.state = mDisplayState;
}
+ mInfo.brightnessMinimum = PowerManager.BRIGHTNESS_MIN;
+ mInfo.brightnessMaximum = PowerManager.BRIGHTNESS_MAX;
+ mInfo.brightnessDefault = mDefaultBrightness;
+
mInfo.ownerUid = mOwnerUid;
mInfo.ownerPackageName = mOwnerPackageName;
@@ -642,6 +658,7 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
private static final int MSG_ON_DISPLAY_PAUSED = 0;
private static final int MSG_ON_DISPLAY_RESUMED = 1;
private static final int MSG_ON_DISPLAY_STOPPED = 2;
+ private static final int MSG_ON_REQUESTED_BRIGHTNESS_CHANGED = 3;
private final IVirtualDisplayCallback mCallback;
@@ -663,6 +680,9 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
case MSG_ON_DISPLAY_STOPPED:
mCallback.onStopped();
break;
+ case MSG_ON_REQUESTED_BRIGHTNESS_CHANGED:
+ mCallback.onRequestedBrightnessChanged((Float) msg.obj);
+ break;
}
} catch (RemoteException e) {
Slog.w(TAG, "Failed to notify listener of virtual display event.", e);
@@ -677,6 +697,11 @@ public class VirtualDisplayAdapter extends DisplayAdapter {
sendEmptyMessage(MSG_ON_DISPLAY_RESUMED);
}
+ public void dispatchRequestedBrightnessChanged(float brightness) {
+ Message msg = obtainMessage(MSG_ON_REQUESTED_BRIGHTNESS_CHANGED, brightness);
+ sendMessage(msg);
+ }
+
public void dispatchDisplayStopped() {
sendEmptyMessage(MSG_ON_DISPLAY_STOPPED);
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 19305dedcb06..76e5ef011789 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -605,7 +605,7 @@ public final class DreamManagerService extends SystemService {
private ComponentName chooseDreamForUser(boolean doze, int userId) {
if (doze) {
ComponentName dozeComponent = getDozeComponent(userId);
- return validateDream(dozeComponent) ? dozeComponent : null;
+ return validateDream(dozeComponent, userId) ? dozeComponent : null;
}
if (mSystemDreamComponent != null) {
@@ -616,11 +616,11 @@ public final class DreamManagerService extends SystemService {
return dreams != null && dreams.length != 0 ? dreams[0] : null;
}
- private boolean validateDream(ComponentName component) {
+ private boolean validateDream(ComponentName component, int userId) {
if (component == null) return false;
- final ServiceInfo serviceInfo = getServiceInfo(component);
+ final ServiceInfo serviceInfo = getServiceInfo(component, userId);
if (serviceInfo == null) {
- Slog.w(TAG, "Dream " + component + " does not exist");
+ Slog.w(TAG, "Dream " + component + " does not exist on user " + userId);
return false;
} else if (serviceInfo.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP
&& !BIND_DREAM_SERVICE.equals(serviceInfo.permission)) {
@@ -647,7 +647,7 @@ public final class DreamManagerService extends SystemService {
List<ComponentName> validComponents = new ArrayList<>();
if (components != null) {
for (ComponentName component : components) {
- if (validateDream(component)) {
+ if (validateDream(component, userId)) {
validComponents.add(component);
}
}
@@ -718,9 +718,10 @@ public final class DreamManagerService extends SystemService {
return userId == mainUserId;
}
- private ServiceInfo getServiceInfo(ComponentName name) {
+ private ServiceInfo getServiceInfo(ComponentName name, int userId) {
+ final Context userContext = mContext.createContextAsUser(UserHandle.of(userId), 0);
try {
- return name != null ? mContext.getPackageManager().getServiceInfo(name,
+ return name != null ? userContext.getPackageManager().getServiceInfo(name,
PackageManager.MATCH_DEBUG_TRIAGED_MISSING) : null;
} catch (NameNotFoundException e) {
return null;
@@ -813,7 +814,7 @@ public final class DreamManagerService extends SystemService {
private void writePulseGestureEnabled() {
ComponentName name = getDozeComponent();
- boolean dozeEnabled = validateDream(name);
+ boolean dozeEnabled = validateDream(name, ActivityManager.getCurrentUser());
LocalServices.getService(InputManagerInternal.class).setPulseGestureEnabled(dozeEnabled);
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index b696c5481205..1b527daafd24 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -273,13 +273,8 @@ public class HdmiCecLocalDevicePlayback extends HdmiCecLocalDeviceSource {
private class DelayedStandbyOnActiveSourceLostRunnable implements Runnable {
@Override
public void run() {
- if (mService.getPowerManagerInternal().wasDeviceIdleFor(
- STANDBY_AFTER_ACTIVE_SOURCE_LOST_DELAY_MS)) {
+ if (!isActiveSource()) {
mService.standby();
- } else {
- mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
- getDeviceInfo().getDeviceType(), Constants.ADDR_TV,
- "DelayedActiveSourceLostStandbyRunnable");
}
}
}
diff --git a/services/core/java/com/android/server/input/AppLaunchShortcutManager.java b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
new file mode 100644
index 000000000000..aef207f9c027
--- /dev/null
+++ b/services/core/java/com/android/server/input/AppLaunchShortcutManager.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright 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.input;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.XmlResourceParser;
+import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
+import android.hardware.input.KeyGestureEvent;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.IndentingPrintWriter;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+
+import com.android.internal.R;
+import com.android.internal.policy.IShortcutService;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Manages quick launch app shortcuts by parsing {@code bookmarks.xml} and intercepting the
+ * correct key combinations for the app shortcuts defined.
+ *
+ * Currently there are 2 ways of defining shortcuts:
+ * - Adding shortcuts to {@code bookmarks.xml}
+ * - Calling into {@code registerShortcutKey()}.
+ */
+final class AppLaunchShortcutManager {
+ private static final String TAG = "AppShortcutManager";
+
+ private static final String TAG_BOOKMARKS = "bookmarks";
+ private static final String TAG_BOOKMARK = "bookmark";
+
+ private static final String ATTRIBUTE_PACKAGE = "package";
+ private static final String ATTRIBUTE_CLASS = "class";
+ private static final String ATTRIBUTE_SHORTCUT = "shortcut";
+ private static final String ATTRIBUTE_CATEGORY = "category";
+ private static final String ATTRIBUTE_SHIFT = "shift";
+ private static final String ATTRIBUTE_ROLE = "role";
+
+ private static final int SHORTCUT_CODE_META_MASK =
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
+ | KeyEvent.META_META_ON;
+
+ private LongSparseArray<IShortcutService> mShortcutKeyServices = new LongSparseArray<>();
+
+ /* Table of Application Launch keys. Maps from key codes to intent categories.
+ *
+ * These are special keys that are used to launch particular kinds of applications,
+ * such as a web browser. HID defines nearly a hundred of them in the Consumer (0x0C)
+ * usage page. We don't support quite that many yet...
+ */
+ private static final SparseArray<String> sApplicationLaunchKeyCategories;
+ private static final SparseArray<String> sApplicationLaunchKeyRoles;
+ static {
+ sApplicationLaunchKeyRoles = new SparseArray<>();
+ sApplicationLaunchKeyCategories = new SparseArray<>();
+ sApplicationLaunchKeyRoles.append(
+ KeyEvent.KEYCODE_EXPLORER, RoleManager.ROLE_BROWSER);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_ENVELOPE, Intent.CATEGORY_APP_EMAIL);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CONTACTS, Intent.CATEGORY_APP_CONTACTS);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALENDAR, Intent.CATEGORY_APP_CALENDAR);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_MUSIC, Intent.CATEGORY_APP_MUSIC);
+ sApplicationLaunchKeyCategories.append(
+ KeyEvent.KEYCODE_CALCULATOR, Intent.CATEGORY_APP_CALCULATOR);
+ }
+
+ private final Context mContext;
+ private boolean mSearchKeyShortcutPending = false;
+ private boolean mConsumeSearchKeyUp = true;
+ private final Map<InputGestureData.Trigger, InputGestureData> mBookmarks = new HashMap<>();
+
+ @SuppressLint("MissingPermission")
+ AppLaunchShortcutManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ loadShortcuts();
+ }
+
+ private void loadShortcuts() {
+ try {
+ XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
+ XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
+ KeyCharacterMap virtualKcm = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
+ break;
+ }
+
+ if (!TAG_BOOKMARK.equals(parser.getName())) {
+ Log.w(TAG, "TAG_BOOKMARK not found");
+ break;
+ }
+
+ String packageName = parser.getAttributeValue(null, ATTRIBUTE_PACKAGE);
+ String className = parser.getAttributeValue(null, ATTRIBUTE_CLASS);
+ String categoryName = parser.getAttributeValue(null, ATTRIBUTE_CATEGORY);
+ String shiftName = parser.getAttributeValue(null, ATTRIBUTE_SHIFT);
+ String roleName = parser.getAttributeValue(null, ATTRIBUTE_ROLE);
+
+ // TODO(b/358569822): Shift bookmarks to use keycode instead of shortcutChar
+ int keycode = KeyEvent.KEYCODE_UNKNOWN;
+ String shortcut = parser.getAttributeValue(null, ATTRIBUTE_SHORTCUT);
+ if (!TextUtils.isEmpty(shortcut)) {
+ KeyEvent[] events = virtualKcm.getEvents(new char[]{shortcut.toLowerCase(
+ Locale.ROOT).charAt(0)});
+ // Single key press can generate the character
+ if (events != null && events.length == 2) {
+ keycode = events[0].getKeyCode();
+ }
+ }
+ if (keycode == KeyEvent.KEYCODE_UNKNOWN) {
+ Log.w(TAG, "Keycode required for bookmark with category=" + categoryName
+ + " packageName=" + packageName + " className=" + className
+ + " role=" + roleName + " shiftName=" + shiftName
+ + " shortcut=" + shortcut);
+ continue;
+ }
+
+ final boolean isShiftShortcut = (shiftName != null && shiftName.toLowerCase(
+ Locale.ROOT).equals("true"));
+ AppLaunchData launchData = null;
+ if (!TextUtils.isEmpty(packageName) && !TextUtils.isEmpty(className)) {
+ launchData = AppLaunchData.createLaunchDataForComponent(packageName, className);
+ } else if (!TextUtils.isEmpty(categoryName)) {
+ launchData = AppLaunchData.createLaunchDataForCategory(categoryName);
+ } else if (!TextUtils.isEmpty(roleName)) {
+ launchData = AppLaunchData.createLaunchDataForRole(roleName);
+ }
+ if (launchData != null) {
+ Log.d(TAG, "adding shortcut " + launchData + "shift="
+ + isShiftShortcut + " keycode=" + keycode);
+ // All bookmarks are based on Action key
+ int modifierState =
+ KeyEvent.META_META_ON | (isShiftShortcut ? KeyEvent.META_SHIFT_ON : 0);
+ InputGestureData bookmark = new InputGestureData.Builder()
+ .setTrigger(InputGestureData.createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION)
+ .setAppLaunchData(launchData)
+ .build();
+ mBookmarks.put(bookmark.getTrigger(), bookmark);
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ Log.e(TAG, "Got exception parsing bookmarks.", e);
+ }
+ }
+
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
+ throws RemoteException {
+ IShortcutService service = mShortcutKeyServices.get(shortcutCode);
+ if (service != null && service.asBinder().pingBinder()) {
+ throw new RemoteException("Key: " + shortcutCode + ", already exists.");
+ }
+
+ mShortcutKeyServices.put(shortcutCode, shortcutService);
+ }
+
+ /**
+ * Handle the shortcut to {@link IShortcutService}
+ * @param keyCode The key code of the event.
+ * @param metaState The meta key modifier state.
+ * @return True if invoked the shortcut, otherwise false.
+ */
+ private boolean handleShortcutService(int keyCode, int metaState) {
+ final long shortcutCodeMeta = metaState & SHORTCUT_CODE_META_MASK;
+ if (shortcutCodeMeta == 0) {
+ return false;
+ }
+ long shortcutCode = keyCode | (shortcutCodeMeta << Integer.SIZE);
+ IShortcutService shortcutService = mShortcutKeyServices.get(shortcutCode);
+ if (shortcutService != null) {
+ try {
+ shortcutService.notifyShortcutKeyPressed(shortcutCode);
+ } catch (RemoteException e) {
+ Log.w(TAG,
+ "Shortcut key service not found, deleting shortcut code: " + shortcutCode);
+ mShortcutKeyServices.delete(shortcutCode);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Handle the shortcut to Launch application.
+ *
+ * @param keyEvent The key event.
+ */
+ @SuppressLint("MissingPermission")
+ @Nullable
+ private AppLaunchData interceptShortcut(KeyEvent keyEvent) {
+ final int keyCode = keyEvent.getKeyCode();
+ final int modifierState = keyEvent.getMetaState() & SHORTCUT_CODE_META_MASK;
+ // Shortcuts are invoked through Search+key, so intercept those here
+ // Any printing key that is chorded with Search should be consumed
+ // even if no shortcut was invoked. This prevents text from being
+ // inadvertently inserted when using a keyboard that has built-in macro
+ // shortcut keys (that emit Search+x) and some of them are not registered.
+ if (mSearchKeyShortcutPending) {
+ KeyCharacterMap kcm = keyEvent.getKeyCharacterMap();
+ if (kcm != null && kcm.isPrintingKey(keyCode)) {
+ mConsumeSearchKeyUp = true;
+ mSearchKeyShortcutPending = false;
+ } else {
+ return null;
+ }
+ } else if (modifierState == 0) {
+ AppLaunchData appLaunchData = null;
+ // Handle application launch keys.
+ String role = sApplicationLaunchKeyRoles.get(keyCode);
+ String category = sApplicationLaunchKeyCategories.get(keyCode);
+ if (!TextUtils.isEmpty(role)) {
+ appLaunchData = AppLaunchData.createLaunchDataForRole(role);
+ } else if (!TextUtils.isEmpty(category)) {
+ appLaunchData = AppLaunchData.createLaunchDataForCategory(category);
+ }
+
+ return appLaunchData;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ InputGestureData gesture = mBookmarks.get(
+ InputGestureData.createKeyTrigger(keyCode, modifierState));
+ if (gesture == null) {
+ return null;
+ }
+ return gesture.getAction().appLaunchData();
+ }
+
+ /**
+ * Handle the shortcut from {@link KeyEvent}
+ *
+ * @param event Description of the key event.
+ */
+ public InterceptKeyResult interceptKey(KeyEvent event) {
+ if (event.getRepeatCount() != 0) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ final int metaState = event.getModifiers();
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_SEARCH) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ mSearchKeyShortcutPending = true;
+ mConsumeSearchKeyUp = false;
+ } else {
+ mSearchKeyShortcutPending = false;
+ if (mConsumeSearchKeyUp) {
+ mConsumeSearchKeyUp = false;
+ return InterceptKeyResult.CONSUME_KEY;
+ }
+ }
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return InterceptKeyResult.DO_NOTHING;
+ }
+
+ // Intercept shortcuts defined in bookmarks or through application launch keycodes
+ AppLaunchData appLaunchData = interceptShortcut(event);
+
+ // TODO(b/358569822): Ideally shortcut service custom shortcuts should be either
+ // migrated to bookmarks or customizable shortcut APIs.
+ if (appLaunchData == null && handleShortcutService(keyCode, metaState)) {
+ return InterceptKeyResult.CONSUME_KEY;
+ }
+
+ return new InterceptKeyResult(/* consumed =*/ false, appLaunchData);
+ }
+
+ /**
+ * @return a list of {@link InputGestureData} containing the application launch shortcuts parsed
+ * at boot time from {@code bookmarks.xml}.
+ */
+ public List<InputGestureData> getBookmarks() {
+ return new ArrayList<>(mBookmarks.values());
+ }
+
+ public void dump(IndentingPrintWriter ipw) {
+ ipw.println("AppLaunchShortcutManager:");
+ ipw.increaseIndent();
+ for (InputGestureData data : mBookmarks.values()) {
+ ipw.println(data);
+ }
+ ipw.decreaseIndent();
+ }
+
+ public record InterceptKeyResult(boolean consumed, @Nullable AppLaunchData appLaunchData) {
+ private static final InterceptKeyResult DO_NOTHING = new InterceptKeyResult(false, null);
+ private static final InterceptKeyResult CONSUME_KEY = new InterceptKeyResult(true, null);
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index f4bd402e63a2..cf1cdaf55e5c 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -16,11 +16,21 @@
package com.android.server.input;
+import static android.hardware.input.InputGestureData.createKeyTrigger;
+import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
+import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
+import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
+import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.content.Context;
import android.hardware.input.InputGestureData;
import android.hardware.input.InputManager;
+import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
+import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -29,15 +39,17 @@ import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* A thread-safe component of {@link InputManagerService} responsible for managing pre-defined input
* gestures and custom gestures defined by other system components using Input APIs.
*
- * TODO(b/365064144): Add implementation to persist data, identify clashes with existing shortcuts.
+ * TODO(b/365064144): Add implementation to persist data.
*
*/
final class InputGestureManager {
@@ -47,13 +59,242 @@ final class InputGestureManager {
KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON
| KeyEvent.META_META_ON;
- @GuardedBy("mCustomInputGestures")
+ private final Context mContext;
+
+ private static final Object mGestureLock = new Object();
+ @GuardedBy("mGestureLock")
private final SparseArray<Map<InputGestureData.Trigger, InputGestureData>>
mCustomInputGestures = new SparseArray<>();
+ @GuardedBy("mGestureLock")
+ private final Map<InputGestureData.Trigger, InputGestureData> mSystemShortcuts =
+ new HashMap<>();
+
+ @GuardedBy("mGestureLock")
+ private final Set<InputGestureData.Trigger> mBlockListedTriggers = new HashSet<>(Set.of(
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_TAB, KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_SPACE,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z,
+ KeyEvent.META_CTRL_ON | KeyEvent.META_ALT_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_C, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_V, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_X, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Z, KeyEvent.META_CTRL_ON),
+ createKeyTrigger(KeyEvent.KEYCODE_Y, KeyEvent.META_CTRL_ON)
+ ));
+
+ public InputGestureManager(Context context) {
+ mContext = context;
+ }
+
+ public void systemRunning() {
+ initSystemShortcuts();
+ blockListBookmarkedTriggers();
+ }
+
+ private void initSystemShortcuts() {
+ // Initialize all system shortcuts
+ List<InputGestureData> systemShortcuts = new ArrayList<>(List.of(
+ createKeyGesture(
+ KeyEvent.KEYCODE_A,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_H,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ENTER,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_I,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_L,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_N,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_S,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_ESCAPE,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_UP,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_DOWN,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_BACK
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_LEFT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_DPAD_RIGHT,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_SLASH,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER
+ ),
+ createKeyGesture(
+ KeyEvent.KEYCODE_TAB,
+ KeyEvent.META_META_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS
+ )
+ ));
+ if (newBugreportKeyboardShortcut() && "1".equals(SystemProperties.get("ro.debuggable"))) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_DEL,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT
+ ));
+ }
+ if (enableMoveToNextDisplayShortcut()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_D,
+ KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY
+ ));
+ }
+ if (keyboardA11yShortcutControl()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_T,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK
+ ));
+ if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_3,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_4,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_5,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS
+ ));
+ }
+ if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_6,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS
+ ));
+ }
+ if (enableTaskResizingKeyboardShortcuts()) {
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_LEFT_BRACKET,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_RIGHT_BRACKET,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW
+ ));
+ systemShortcuts.add(createKeyGesture(
+ KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE
+ ));
+ }
+ }
+ synchronized (mGestureLock) {
+ for (InputGestureData systemShortcut : systemShortcuts) {
+ mSystemShortcuts.put(systemShortcut.getTrigger(), systemShortcut);
+ }
+ }
+ }
+
+ private void blockListBookmarkedTriggers() {
+ synchronized (mGestureLock) {
+ InputManager im = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ for (InputGestureData bookmark : im.getAppLaunchBookmarks()) {
+ mBlockListedTriggers.add(bookmark.getTrigger());
+ }
+ }
+ }
+
@InputManager.CustomInputGestureResult
public int addCustomInputGesture(int userId, InputGestureData newGesture) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
+ if (mBlockListedTriggers.contains(newGesture.getTrigger())
+ || mSystemShortcuts.containsKey(newGesture.getTrigger())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ if (newGesture.getTrigger() instanceof InputGestureData.KeyTrigger keyTrigger) {
+ if (KeyEvent.isModifierKey(keyTrigger.getKeycode()) ||
+ KeyEvent.isSystemKey(keyTrigger.getKeycode())) {
+ return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE;
+ }
+ }
if (!mCustomInputGestures.contains(userId)) {
mCustomInputGestures.put(userId, new HashMap<>());
}
@@ -69,7 +310,7 @@ final class InputGestureManager {
@InputManager.CustomInputGestureResult
public int removeCustomInputGesture(int userId, InputGestureData data) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
if (!mCustomInputGestures.contains(userId)) {
return InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_DOES_NOT_EXIST;
}
@@ -88,14 +329,14 @@ final class InputGestureManager {
}
public void removeAllCustomInputGestures(int userId) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
mCustomInputGestures.remove(userId);
}
}
@NonNull
public List<InputGestureData> getCustomInputGestures(int userId) {
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
if (!mCustomInputGestures.contains(userId)) {
return List.of();
}
@@ -109,7 +350,7 @@ final class InputGestureManager {
if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
return null;
}
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
Map<InputGestureData.Trigger, InputGestureData> customGestures =
mCustomInputGestures.get(userId);
if (customGestures == null) {
@@ -120,10 +361,44 @@ final class InputGestureManager {
}
}
+ @Nullable
+ public InputGestureData getSystemShortcutForKeyEvent(KeyEvent event) {
+ final int keyCode = event.getKeyCode();
+ if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {
+ return null;
+ }
+ synchronized (mGestureLock) {
+ int modifierState = event.getMetaState() & KEY_GESTURE_META_MASK;
+ return mSystemShortcuts.get(InputGestureData.createKeyTrigger(keyCode, modifierState));
+ }
+ }
+
+ private static InputGestureData createKeyGesture(int keycode, int modifierState,
+ int keyGestureType) {
+ return new InputGestureData.Builder()
+ .setTrigger(createKeyTrigger(keycode, modifierState))
+ .setKeyGestureType(keyGestureType)
+ .build();
+ }
+
public void dump(IndentingPrintWriter ipw) {
ipw.println("InputGestureManager:");
ipw.increaseIndent();
- synchronized (mCustomInputGestures) {
+ synchronized (mGestureLock) {
+ ipw.println("System Shortcuts:");
+ ipw.increaseIndent();
+ for (InputGestureData systemShortcut : mSystemShortcuts.values()) {
+ ipw.println(systemShortcut);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Blocklisted Triggers:");
+ ipw.increaseIndent();
+ for (InputGestureData.Trigger blocklistedTrigger : mBlockListedTriggers) {
+ ipw.println(blocklistedTrigger);
+ }
+ ipw.decreaseIndent();
+ ipw.println("Custom Gestures:");
+ ipw.increaseIndent();
int size = mCustomInputGestures.size();
for (int i = 0; i < size; i++) {
Map<InputGestureData.Trigger, InputGestureData> customGestures =
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 1c5bd59fa386..265e4531a10a 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -23,11 +23,13 @@ import android.graphics.PointF;
import android.hardware.display.DisplayViewport;
import android.hardware.input.KeyGestureEvent;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.SparseBooleanArray;
import android.view.InputChannel;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
+import com.android.internal.policy.IShortcutService;
import java.util.List;
@@ -278,6 +280,15 @@ public abstract class InputManagerInternal {
*/
public abstract void setAccessibilityPointerIconScaleFactor(int displayId, float scaleFactor);
+
+ /**
+ * Register shortcuts for input manager to dispatch.
+ * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+ * @hide
+ */
+ public abstract void registerShortcutKey(long shortcutCode,
+ IShortcutService shortcutKeyReceiver) throws RemoteException;
+
/**
* Set whether the given input device can wake up the kernel from sleep
* when it generates input events. By default, usually only internal (built-in)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index eefa15e1cbaa..78e3b846f9dc 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -129,6 +129,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodSubtypeHandle;
import com.android.internal.os.SomeArgs;
+import com.android.internal.policy.IShortcutService;
import com.android.internal.policy.KeyInterceptionInfo;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
@@ -3025,6 +3026,11 @@ public class InputManagerService extends IInputManager.Stub
return mKeyGestureController.getCustomInputGestures(UserHandle.getCallingUserId());
}
+ @Override
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ return mKeyGestureController.getAppLaunchBookmarks();
+ }
+
private void handleCurrentUserChanged(@UserIdInt int userId) {
mCurrentUserId = userId;
mKeyGestureController.setCurrentUserId(userId);
@@ -3569,6 +3575,12 @@ public class InputManagerService extends IInputManager.Stub
}
@Override
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mKeyGestureController.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ @Override
public boolean setKernelWakeEnabled(int deviceId, boolean enabled) {
return mNative.setKernelWakeEnabled(deviceId, enabled);
}
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
index 96ef07004b38..e0991ec20f9e 100644
--- a/services/core/java/com/android/server/input/KeyGestureController.java
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -20,12 +20,8 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK;
import static android.content.pm.PackageManager.FEATURE_WATCH;
import static android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE;
-import static com.android.hardware.input.Flags.keyboardA11yShortcutControl;
import static com.android.hardware.input.Flags.useKeyGestureEventHandler;
import static com.android.hardware.input.Flags.useKeyGestureEventHandlerMultiPressGestures;
-import static com.android.server.flags.Flags.newBugreportKeyboardShortcut;
-import static com.android.window.flags.Flags.enableMoveToNextDisplayShortcut;
-import static com.android.window.flags.Flags.enableTaskResizingKeyboardShortcuts;
import android.annotation.BinderThread;
import android.annotation.MainThread;
@@ -69,6 +65,7 @@ import android.view.KeyEvent;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.IShortcutService;
import com.android.server.policy.KeyCombinationManager;
import java.util.ArrayDeque;
@@ -119,7 +116,8 @@ final class KeyGestureController {
private final int mSystemPid;
private final KeyCombinationManager mKeyCombinationManager;
private final SettingsObserver mSettingsObserver;
- private final InputGestureManager mInputGestureManager = new InputGestureManager();
+ private final AppLaunchShortcutManager mAppLaunchShortcutManager;
+ private final InputGestureManager mInputGestureManager;
private static final Object mUserLock = new Object();
@UserIdInt
@GuardedBy("mUserLock")
@@ -131,7 +129,6 @@ final class KeyGestureController {
private boolean mPendingHideRecentSwitcher;
// Platform behaviors
- private boolean mEnableBugReportKeyboardShortcut;
private boolean mHasFeatureWatch;
private boolean mHasFeatureLeanback;
@@ -178,13 +175,13 @@ final class KeyGestureController {
});
mKeyCombinationManager = new KeyCombinationManager(mHandler);
mSettingsObserver = new SettingsObserver(mHandler);
+ mAppLaunchShortcutManager = new AppLaunchShortcutManager(mContext);
+ mInputGestureManager = new InputGestureManager(mContext);
initBehaviors();
initKeyCombinationRules();
}
private void initBehaviors() {
- mEnableBugReportKeyboardShortcut = "1".equals(SystemProperties.get("ro.debuggable"));
-
PackageManager pm = mContext.getPackageManager();
mHasFeatureWatch = pm.hasSystemFeature(FEATURE_WATCH);
mHasFeatureLeanback = pm.hasSystemFeature(FEATURE_LEANBACK);
@@ -437,6 +434,8 @@ final class KeyGestureController {
public void systemRunning() {
mSettingsObserver.observe();
+ mAppLaunchShortcutManager.systemRunning();
+ mInputGestureManager.systemRunning();
}
public boolean interceptKeyBeforeQueueing(KeyEvent event, int policyFlags) {
@@ -513,15 +512,34 @@ final class KeyGestureController {
mPendingCapsLockToggle = false;
}
+ // Handle App launch shortcuts
+ AppLaunchShortcutManager.InterceptKeyResult result = mAppLaunchShortcutManager.interceptKey(
+ event);
+ if (result.consumed()) {
+ return true;
+ }
+ if (result.appLaunchData() != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
+ focusedToken, /* flags = */0, result.appLaunchData());
+ }
+
+ // Handle system shortcuts
+ if (firstDown) {
+ InputGestureData systemShortcut = mInputGestureManager.getSystemShortcutForKeyEvent(
+ event);
+ if (systemShortcut != null) {
+ return handleKeyGesture(deviceId, new int[]{keyCode}, metaState,
+ systemShortcut.getAction().keyGestureType(),
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE,
+ displayId, focusedToken, /* flags = */0,
+ systemShortcut.getAction().appLaunchData());
+ }
+ }
+
+ // Handle system keys
switch (keyCode) {
- case KeyEvent.KEYCODE_A:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
case KeyEvent.KEYCODE_RECENT_APPS:
if (firstDown) {
handleKeyGesture(deviceId, new int[]{keyCode}, /* modifierState = */0,
@@ -544,257 +562,6 @@ final class KeyGestureController {
/* appLaunchData = */null);
}
return true;
- case KeyEvent.KEYCODE_H:
- case KeyEvent.KEYCODE_ENTER:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_I:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_L:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_N:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_S:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_T:
- if (keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_3:
- if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_4:
- if (InputSettings.isAccessibilityMouseKeysFeatureFlagEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_5:
- if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_6:
- if (InputSettings.isAccessibilitySlowKeysFeatureFlagEnabled()
- && keyboardA11yShortcutControl()) {
- if (firstDown && event.isMetaPressed() && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_DEL:
- if (newBugreportKeyboardShortcut()) {
- if (firstDown && mEnableBugReportKeyboardShortcut && event.isMetaPressed()
- && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- // fall through
- case KeyEvent.KEYCODE_ESCAPE:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_UP:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_DOWN:
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
- case KeyEvent.KEYCODE_DPAD_LEFT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_LEFT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_DPAD_RIGHT:
- if (firstDown && event.isMetaPressed()) {
- if (event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS_RIGHT,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_D:
- if (enableMoveToNextDisplayShortcut()) {
- if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_META_ON | KeyEvent.META_CTRL_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_LEFT_BRACKET:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_RIGHT_BRACKET:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_EQUALS:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_MINUS:
- if (enableTaskResizingKeyboardShortcuts()) {
- if (firstDown && event.isAltPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode},
- KeyEvent.META_ALT_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE,
- displayId, focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- }
- break;
- case KeyEvent.KEYCODE_SLASH:
- if (firstDown && event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- }
- break;
case KeyEvent.KEYCODE_BRIGHTNESS_UP:
case KeyEvent.KEYCODE_BRIGHTNESS_DOWN:
if (down) {
@@ -938,12 +705,7 @@ final class KeyGestureController {
return true;
case KeyEvent.KEYCODE_TAB:
if (firstDown) {
- if (event.isMetaPressed()) {
- return handleKeyGesture(deviceId, new int[]{keyCode}, KeyEvent.META_META_ON,
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE, displayId,
- focusedToken, /* flags = */0, /* appLaunchData = */null);
- } else if (!mPendingHideRecentSwitcher) {
+ if (!mPendingHideRecentSwitcher) {
final int shiftlessModifiers =
event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
if (KeyEvent.metaStateHasModifiers(
@@ -1004,6 +766,7 @@ final class KeyGestureController {
return true;
}
+ // Handle custom shortcuts
if (firstDown) {
InputGestureData customGesture;
synchronized (mUserLock) {
@@ -1258,6 +1021,16 @@ final class KeyGestureController {
return result;
}
+ @BinderThread
+ public AidlInputGestureData[] getAppLaunchBookmarks() {
+ List<InputGestureData> bookmarks = mAppLaunchShortcutManager.getBookmarks();
+ AidlInputGestureData[] result = new AidlInputGestureData[bookmarks.size()];
+ for (int i = 0; i < bookmarks.size(); i++) {
+ result[i] = bookmarks.get(i).getAidlData();
+ }
+ return result;
+ }
+
private void onKeyGestureEventListenerDied(int pid) {
synchronized (mKeyGestureEventListenerRecords) {
mKeyGestureEventListenerRecords.remove(pid);
@@ -1329,6 +1102,15 @@ final class KeyGestureController {
}
}
+ public void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+ throws RemoteException {
+ mAppLaunchShortcutManager.registerShortcutKey(shortcutCode, shortcutKeyReceiver);
+ }
+
+ public List<InputGestureData> getBookmarks() {
+ return mAppLaunchShortcutManager.getBookmarks();
+ }
+
private void onKeyGestureHandlerDied(int pid) {
synchronized (mKeyGestureHandlerRecords) {
mKeyGestureHandlerRecords.remove(pid);
@@ -1471,6 +1253,7 @@ final class KeyGestureController {
}
ipw.decreaseIndent();
mKeyCombinationManager.dump("", ipw);
+ mAppLaunchShortcutManager.dump(ipw);
mInputGestureManager.dump(ipw);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
index 146ce1732070..46be1ca0eec2 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeUtils.java
@@ -27,6 +27,7 @@ import android.util.ArrayMap;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.Xml;
+import android.view.inputmethod.Flags;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -59,8 +60,14 @@ final class AdditionalSubtypeUtils {
private static final String NODE_SUBTYPE = "subtype";
private static final String NODE_IMI = "imi";
private static final String ATTR_ID = "id";
+ /** The resource ID of the subtype name. */
private static final String ATTR_LABEL = "label";
+ /** The untranslatable name of the subtype. */
private static final String ATTR_NAME_OVERRIDE = "nameOverride";
+ /** The layout label string resource identifier. */
+ private static final String ATTR_LAYOUT_LABEL = "layoutLabel";
+ /** The non-localized layout label. */
+ private static final String ATTR_LAYOUT_LABEL_NON_LOCALIZED = "layoutLabelNonLocalized";
private static final String ATTR_NAME_PK_LANGUAGE_TAG = "pkLanguageTag";
private static final String ATTR_NAME_PK_LAYOUT_TYPE = "pkLayoutType";
private static final String ATTR_ICON = "icon";
@@ -173,6 +180,11 @@ final class AdditionalSubtypeUtils {
out.attributeInt(null, ATTR_ICON, subtype.getIconResId());
out.attributeInt(null, ATTR_LABEL, subtype.getNameResId());
out.attribute(null, ATTR_NAME_OVERRIDE, subtype.getNameOverride().toString());
+ if (Flags.imeSwitcherRevampApi()) {
+ out.attributeInt(null, ATTR_LAYOUT_LABEL, subtype.getLayoutLabelResource());
+ out.attribute(null, ATTR_LAYOUT_LABEL_NON_LOCALIZED,
+ subtype.getLayoutLabelNonLocalized().toString());
+ }
ULocale pkLanguageTag = subtype.getPhysicalKeyboardHintLanguageTag();
if (pkLanguageTag != null) {
out.attribute(null, ATTR_NAME_PK_LANGUAGE_TAG,
@@ -264,6 +276,16 @@ final class AdditionalSubtypeUtils {
final int label = parser.getAttributeInt(null, ATTR_LABEL);
final String untranslatableName = parser.getAttributeValue(null,
ATTR_NAME_OVERRIDE);
+ final int layoutLabelResource;
+ final String layoutLabelNonLocalized;
+ if (Flags.imeSwitcherRevampApi()) {
+ layoutLabelResource = parser.getAttributeInt(null, ATTR_LAYOUT_LABEL);
+ layoutLabelNonLocalized = parser.getAttributeValue(null,
+ ATTR_LAYOUT_LABEL_NON_LOCALIZED);
+ } else {
+ layoutLabelResource = 0;
+ layoutLabelNonLocalized = null;
+ }
final String pkLanguageTag = parser.getAttributeValue(null,
ATTR_NAME_PK_LANGUAGE_TAG);
final String pkLayoutType = parser.getAttributeValue(null,
@@ -283,6 +305,7 @@ final class AdditionalSubtypeUtils {
final InputMethodSubtype.InputMethodSubtypeBuilder
builder = new InputMethodSubtype.InputMethodSubtypeBuilder()
.setSubtypeNameResId(label)
+ .setLayoutLabelResource(layoutLabelResource)
.setPhysicalKeyboardHint(
pkLanguageTag == null ? null : new ULocale(pkLanguageTag),
pkLayoutType == null ? "" : pkLayoutType)
@@ -301,6 +324,9 @@ final class AdditionalSubtypeUtils {
if (untranslatableName != null) {
builder.setSubtypeNameOverride(untranslatableName);
}
+ if (layoutLabelNonLocalized != null) {
+ builder.setLayoutLabelNonLocalized(layoutLabelNonLocalized);
+ }
tempSubtypesArray.add(builder.build());
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 010437337ba1..d8483f721306 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4969,7 +4969,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
setImeVisibilityOnFocusedWindowClient(false, userData,
- null /* TODO(b329229469) check statsToken */);
+ null /* TODO(b/353463205) check statsToken */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
index 6abd5aabfabf..9f94905686fe 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuControllerNew.java
@@ -238,8 +238,8 @@ final class InputMethodMenuControllerNew {
prevImeId = imeId;
}
- menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mImi,
- item.mSubtypeIndex));
+ menuItems.add(new SubtypeItem(item.mImeName, item.mSubtypeName, item.mLayoutName,
+ item.mImi, item.mSubtypeIndex));
}
return menuItems;
@@ -348,6 +348,13 @@ final class InputMethodMenuControllerNew {
@Nullable
final CharSequence mSubtypeName;
+ /**
+ * The name of the subtype's layout, or {@code null} if this item doesn't have a subtype,
+ * or doesn't specify a layout.
+ */
+ @Nullable
+ private final CharSequence mLayoutName;
+
/** The info of the input method. */
@NonNull
final InputMethodInfo mImi;
@@ -360,10 +367,11 @@ final class InputMethodMenuControllerNew {
final int mSubtypeIndex;
SubtypeItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi,
+ @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi,
@IntRange(from = NOT_A_SUBTYPE_INDEX) int subtypeIndex) {
mImeName = imeName;
mSubtypeName = subtypeName;
+ mLayoutName = layoutName;
mImi = imi;
mSubtypeIndex = subtypeIndex;
}
@@ -521,6 +529,9 @@ final class InputMethodMenuControllerNew {
/** The name of the item. */
@NonNull
private final TextView mName;
+ /** The layout name. */
+ @NonNull
+ private final TextView mLayout;
/** Indicator for the selected status of the item. */
@NonNull
private final ImageView mCheckmark;
@@ -536,6 +547,7 @@ final class InputMethodMenuControllerNew {
mContainer = itemView;
mName = itemView.requireViewById(com.android.internal.R.id.text);
+ mLayout = itemView.requireViewById(com.android.internal.R.id.text2);
mCheckmark = itemView.requireViewById(com.android.internal.R.id.image);
mContainer.setOnClickListener((v) -> {
@@ -563,6 +575,9 @@ final class InputMethodMenuControllerNew {
// Trigger the ellipsize marquee behaviour by selecting the name.
mName.setSelected(isSelected);
mName.setText(name);
+ mLayout.setText(item.mLayoutName);
+ mLayout.setVisibility(
+ !TextUtils.isEmpty(item.mLayoutName) ? View.VISIBLE : View.GONE);
mCheckmark.setVisibility(isSelected ? View.VISIBLE : View.GONE);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 96b3e084d102..51b85e90c447 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -85,6 +85,12 @@ final class InputMethodSubtypeSwitchingController {
public final CharSequence mImeName;
@Nullable
public final CharSequence mSubtypeName;
+ /**
+ * The subtype's layout name, or {@code null} if this item doesn't have a subtype,
+ * or doesn't specify a layout.
+ */
+ @Nullable
+ public final CharSequence mLayoutName;
@NonNull
public final InputMethodInfo mImi;
/**
@@ -96,10 +102,11 @@ final class InputMethodSubtypeSwitchingController {
public final boolean mIsSystemLanguage;
ImeSubtypeListItem(@NonNull CharSequence imeName, @Nullable CharSequence subtypeName,
- @NonNull InputMethodInfo imi, int subtypeIndex, @Nullable String subtypeLocale,
- @NonNull String systemLocale) {
+ @Nullable CharSequence layoutName, @NonNull InputMethodInfo imi, int subtypeIndex,
+ @Nullable String subtypeLocale, @NonNull String systemLocale) {
mImeName = imeName;
mSubtypeName = subtypeName;
+ mLayoutName = layoutName;
mImi = imi;
mSubtypeIndex = subtypeIndex;
if (TextUtils.isEmpty(subtypeLocale)) {
@@ -252,8 +259,11 @@ final class InputMethodSubtypeSwitchingController {
subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
.getDisplayName(userAwareContext, imi.getPackageName(),
imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel,
- subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+ : subtype.getLayoutDisplayName(userAwareContext,
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+ imi, j, subtype.getLocale(), mSystemLocaleStr));
// Removing this subtype from enabledSubtypeSet because we no
// longer need to add an entry of this subtype to imList to avoid
@@ -262,8 +272,8 @@ final class InputMethodSubtypeSwitchingController {
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
- mSystemLocaleStr));
+ imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+ null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
}
}
Collections.sort(imList);
@@ -311,13 +321,16 @@ final class InputMethodSubtypeSwitchingController {
subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
.getDisplayName(userAwareContext, imi.getPackageName(),
imi.getServiceInfo().applicationInfo);
- imList.add(new ImeSubtypeListItem(imeLabel,
- subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
+ final var layoutName = subtype.overridesImplicitlyEnabledSubtype() ? null
+ : subtype.getLayoutDisplayName(userAwareContext,
+ imi.getServiceInfo().applicationInfo);
+ imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, layoutName,
+ imi, j, subtype.getLocale(), mSystemLocaleStr));
}
}
} else {
- imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_INDEX, null,
- mSystemLocaleStr));
+ imList.add(new ImeSubtypeListItem(imeLabel, null /* subtypeName */,
+ null /* layoutName */, imi, NOT_A_SUBTYPE_INDEX, null, mSystemLocaleStr));
}
}
return imList;
diff --git a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java b/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
deleted file mode 100644
index e831e40e70d1..000000000000
--- a/services/core/java/com/android/server/integrity/parser/RuleMetadataParser.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.parser;
-
-import android.annotation.Nullable;
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Helper class for parsing rule metadata. */
-public class RuleMetadataParser {
-
- public static final String RULE_PROVIDER_TAG = "P";
- public static final String VERSION_TAG = "V";
-
- /** Parse the rule metadata from an input stream. */
- @Nullable
- public static RuleMetadata parse(InputStream inputStream)
- throws XmlPullParserException, IOException {
-
- String ruleProvider = "";
- String version = "";
-
- TypedXmlPullParser xmlPullParser = Xml.resolvePullParser(inputStream);
-
- int eventType;
- while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
- if (eventType == XmlPullParser.START_TAG) {
- String tag = xmlPullParser.getName();
- switch (tag) {
- case RULE_PROVIDER_TAG:
- ruleProvider = xmlPullParser.nextText();
- break;
- case VERSION_TAG:
- version = xmlPullParser.nextText();
- break;
- default:
- throw new IllegalStateException("Unknown tag in metadata: " + tag);
- }
- }
- }
-
- return new RuleMetadata(ruleProvider, version);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
deleted file mode 100644
index 8ba5870aef0f..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.INSTALLER_ALLOWED_BY_MANIFEST_START;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.InstallerAllowedByManifestFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import com.android.internal.util.Preconditions;
-import com.android.server.integrity.model.BitOutputStream;
-import com.android.server.integrity.model.ByteTrackedOutputStream;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
-/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
-public class RuleBinarySerializer implements RuleSerializer {
- static final int TOTAL_RULE_SIZE_LIMIT = 200000;
- static final int INDEXED_RULE_SIZE_LIMIT = 100000;
- static final int NONINDEXED_RULE_SIZE_LIMIT = 1000;
-
- // Get the byte representation for a list of rules.
- @Override
- public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException {
- try {
- ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
- serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
- return rulesOutputStream.toByteArray();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- // Get the byte representation for a list of rules, and write them to an output stream.
- @Override
- public void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream rulesFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException {
- try {
- if (rules == null) {
- throw new IllegalArgumentException("Null rules cannot be serialized.");
- }
-
- if (rules.size() > TOTAL_RULE_SIZE_LIMIT) {
- throw new IllegalArgumentException("Too many rules provided: " + rules.size());
- }
-
- // Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, Map<String, List<Rule>>> indexedRules =
- RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
-
- // Validate the rule blocks are not larger than expected limits.
- verifySize(indexedRules.get(PACKAGE_NAME_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(APP_CERTIFICATE_INDEXED), INDEXED_RULE_SIZE_LIMIT);
- verifySize(indexedRules.get(NOT_INDEXED), NONINDEXED_RULE_SIZE_LIMIT);
-
- // Serialize the rules.
- ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
- new ByteTrackedOutputStream(rulesFileOutputStream);
- serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> packageNameIndexes =
- serializeRuleList(
- indexedRules.get(PACKAGE_NAME_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> appCertificateIndexes =
- serializeRuleList(
- indexedRules.get(APP_CERTIFICATE_INDEXED),
- ruleFileByteTrackedOutputStream);
- LinkedHashMap<String, Integer> unindexedRulesIndexes =
- serializeRuleList(
- indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
-
- // Serialize their indexes.
- BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
- serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
- serializeIndexGroup(
- unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
- indexingBitOutputStream.flush();
- } catch (Exception e) {
- throw new RuleSerializeException(e.getMessage(), e);
- }
- }
-
- private void verifySize(Map<String, List<Rule>> ruleListMap, int ruleSizeLimit) {
- int totalRuleCount =
- ruleListMap.values().stream()
- .map(list -> list.size())
- .collect(Collectors.summingInt(Integer::intValue));
- if (totalRuleCount > ruleSizeLimit) {
- throw new IllegalArgumentException(
- "Too many rules provided in the indexing group. Provided "
- + totalRuleCount
- + " limit "
- + ruleSizeLimit);
- }
- }
-
- private void serializeRuleFileMetadata(
- Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
- throws IOException {
- int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- bitOutputStream.flush();
- }
-
- private LinkedHashMap<String, Integer> serializeRuleList(
- Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
- throws IOException {
- Preconditions.checkArgument(
- rulesMap != null, "serializeRuleList should never be called with null rule list.");
-
- BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
- LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
- indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
- int indexTracker = 0;
- for (String key : sortedKeys) {
- if (indexTracker >= INDEXING_BLOCK_SIZE) {
- indexMapping.put(key, outputStream.getWrittenBytesCount());
- indexTracker = 0;
- }
-
- for (Rule rule : rulesMap.get(key)) {
- serializeRule(rule, bitOutputStream);
- bitOutputStream.flush();
- indexTracker++;
- }
- }
- indexMapping.put(END_INDEXING_KEY, outputStream.getWrittenBytesCount());
-
- return indexMapping;
- }
-
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
- if (rule == null) {
- throw new IllegalArgumentException("Null rule can not be serialized");
- }
-
- // Start with a '1' bit to mark the start of a rule.
- bitOutputStream.setNext();
-
- serializeFormula(rule.getFormula(), bitOutputStream);
- bitOutputStream.setNext(EFFECT_BITS, rule.getEffect());
-
- // End with a '1' bit to mark the end of a rule.
- bitOutputStream.setNext();
- }
-
- private void serializeFormula(IntegrityFormula formula, BitOutputStream bitOutputStream)
- throws IOException {
- if (formula instanceof AtomicFormula) {
- serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
- } else if (formula instanceof CompoundFormula) {
- serializeCompoundFormula((CompoundFormula) formula, bitOutputStream);
- } else if (formula instanceof InstallerAllowedByManifestFormula) {
- bitOutputStream.setNext(SEPARATOR_BITS, INSTALLER_ALLOWED_BY_MANIFEST_START);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid formula type: %s", formula.getClass()));
- }
- }
-
- private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
- if (compoundFormula == null) {
- throw new IllegalArgumentException("Null compound formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_START);
- bitOutputStream.setNext(CONNECTOR_BITS, compoundFormula.getConnector());
- for (IntegrityFormula formula : compoundFormula.getFormulas()) {
- serializeFormula(formula, bitOutputStream);
- }
- bitOutputStream.setNext(SEPARATOR_BITS, COMPOUND_FORMULA_END);
- }
-
- private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
- if (atomicFormula == null) {
- throw new IllegalArgumentException("Null atomic formula can not be serialized");
- }
-
- bitOutputStream.setNext(SEPARATOR_BITS, ATOMIC_FORMULA_START);
- bitOutputStream.setNext(KEY_BITS, atomicFormula.getKey());
- if (atomicFormula.getTag() == AtomicFormula.STRING_ATOMIC_FORMULA_TAG) {
- AtomicFormula.StringAtomicFormula stringAtomicFormula =
- (AtomicFormula.StringAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeStringValue(
- stringAtomicFormula.getValue(),
- stringAtomicFormula.getIsHashedValue(),
- bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.LONG_ATOMIC_FORMULA_TAG) {
- AtomicFormula.LongAtomicFormula longAtomicFormula =
- (AtomicFormula.LongAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, longAtomicFormula.getOperator());
- // TODO(b/147880712): Temporary hack until we support long values in bitOutputStream
- long value = longAtomicFormula.getValue();
- serializeIntValue((int) (value >>> 32), bitOutputStream);
- serializeIntValue((int) value, bitOutputStream);
- } else if (atomicFormula.getTag() == AtomicFormula.BOOLEAN_ATOMIC_FORMULA_TAG) {
- AtomicFormula.BooleanAtomicFormula booleanAtomicFormula =
- (AtomicFormula.BooleanAtomicFormula) atomicFormula;
- bitOutputStream.setNext(OPERATOR_BITS, AtomicFormula.EQ);
- serializeBooleanValue(booleanAtomicFormula.getValue(), bitOutputStream);
- } else {
- throw new IllegalArgumentException(
- String.format("Invalid atomic formula type: %s", atomicFormula.getClass()));
- }
- }
-
- private void serializeIndexGroup(
- LinkedHashMap<String, Integer> indexes,
- BitOutputStream bitOutputStream,
- boolean isIndexed)
- throws IOException {
- // Output the starting location of this indexing group.
- serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
-
- // If the group is indexed, output the locations of the indexes.
- if (isIndexed) {
- for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
- if (!entry.getKey().equals(START_INDEXING_KEY)
- && !entry.getKey().equals(END_INDEXING_KEY)) {
- serializeStringValue(
- entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
- serializeIntValue(entry.getValue(), bitOutputStream);
- }
- }
- }
-
- // Output the end location of this indexing group.
- serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
- serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
- }
-
- private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream)
- throws IOException {
- if (value == null) {
- throw new IllegalArgumentException("String value can not be null.");
- }
- byte[] valueBytes = getBytesForString(value, isHashedValue);
-
- bitOutputStream.setNext(isHashedValue);
- bitOutputStream.setNext(VALUE_SIZE_BITS, valueBytes.length);
- for (byte valueByte : valueBytes) {
- bitOutputStream.setNext(/* numOfBits= */ 8, valueByte);
- }
- }
-
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
- bitOutputStream.setNext(/* numOfBits= */ 32, value);
- }
-
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
- throws IOException {
- bitOutputStream.setNext(value);
- }
-
- // Get the byte array for a value.
- // If the value is not hashed, use its byte array form directly.
- // If the value is hashed, get the raw form decoding of the value. All hashed values are
- // hex-encoded. Serialized values are in raw form.
- private static byte[] getBytesForString(String value, boolean isHashedValue) {
- if (!isHashedValue) {
- return value.getBytes(StandardCharsets.UTF_8);
- }
- return IntegrityUtils.getBytesFromHexDigest(value);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
deleted file mode 100644
index 2cbd4ede5214..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.annotation.IntDef;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/** Holds the indexing type and indexing key of a given formula. */
-class RuleIndexingDetails {
-
- static final int NOT_INDEXED = 0;
- static final int PACKAGE_NAME_INDEXED = 1;
- static final int APP_CERTIFICATE_INDEXED = 2;
-
- static final String DEFAULT_RULE_KEY = "N/A";
-
- /** Represents which indexed file the rule should be located. */
- @IntDef(
- value = {
- NOT_INDEXED,
- PACKAGE_NAME_INDEXED,
- APP_CERTIFICATE_INDEXED
- })
- @Retention(RetentionPolicy.SOURCE)
- public @interface IndexType {
- }
-
- private @IndexType int mIndexType;
- private String mRuleKey;
-
- /** Constructor without a ruleKey for {@code NOT_INDEXED}. */
- RuleIndexingDetails(@IndexType int indexType) {
- this.mIndexType = indexType;
- this.mRuleKey = DEFAULT_RULE_KEY;
- }
-
- /** Constructor with a ruleKey for indexed rules. */
- RuleIndexingDetails(@IndexType int indexType, String ruleKey) {
- this.mIndexType = indexType;
- this.mRuleKey = ruleKey;
- }
-
- /** Returns the indexing type for the rule. */
- @IndexType
- public int getIndexType() {
- return mIndexType;
- }
-
- /** Returns the identified rule key. */
- public String getRuleKey() {
- return mRuleKey;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
deleted file mode 100644
index e7235591fb9b..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-
-/** A helper class for identifying the indexing type and key of a given rule. */
-class RuleIndexingDetailsIdentifier {
-
- /**
- * Splits a given rule list into three indexing categories. Each rule category is returned as a
- * TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
- * PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
- * NOT_INDEXED rules.
- */
- public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
- List<Rule> rules) {
- if (rules == null) {
- throw new IllegalArgumentException(
- "Index buckets cannot be created for null rule list.");
- }
-
- Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
- typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
- typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
- typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
-
- // Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
- // entries sorted by their index key.
- for (Rule rule : rules) {
- RuleIndexingDetails indexingDetails;
- try {
- indexingDetails = getIndexingDetails(rule.getFormula());
- } catch (Exception e) {
- throw new IllegalArgumentException(
- String.format("Malformed rule identified. [%s]", rule.toString()));
- }
-
- int ruleIndexType = indexingDetails.getIndexType();
- String ruleKey = indexingDetails.getRuleKey();
-
- if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
- typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
- }
-
- typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
- }
-
- return typeOrganizedRuleMap;
- }
-
- private static RuleIndexingDetails getIndexingDetails(IntegrityFormula formula) {
- switch (formula.getTag()) {
- case IntegrityFormula.COMPOUND_FORMULA_TAG:
- return getIndexingDetailsForCompoundFormula((CompoundFormula) formula);
- case IntegrityFormula.STRING_ATOMIC_FORMULA_TAG:
- return getIndexingDetailsForStringAtomicFormula(
- (AtomicFormula.StringAtomicFormula) formula);
- case IntegrityFormula.LONG_ATOMIC_FORMULA_TAG:
- case IntegrityFormula.INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
- case IntegrityFormula.BOOLEAN_ATOMIC_FORMULA_TAG:
- // Package name and app certificate related formulas are string atomic formulas.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- throw new IllegalArgumentException(
- String.format("Invalid formula tag type: %s", formula.getTag()));
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForCompoundFormula(
- CompoundFormula compoundFormula) {
- int connector = compoundFormula.getConnector();
- List<IntegrityFormula> formulas = compoundFormula.getFormulas();
-
- switch (connector) {
- case CompoundFormula.AND:
- case CompoundFormula.OR:
- // If there is a package name related atomic rule, return package name indexed.
- Optional<RuleIndexingDetails> packageNameRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == PACKAGE_NAME_INDEXED)
- .findAny();
- if (packageNameRule.isPresent()) {
- return packageNameRule.get();
- }
-
- // If there is an app certificate related atomic rule but no package name related
- // atomic rule, return app certificate indexed.
- Optional<RuleIndexingDetails> appCertificateRule =
- formulas.stream()
- .map(formula -> getIndexingDetails(formula))
- .filter(ruleIndexingDetails -> ruleIndexingDetails.getIndexType()
- == APP_CERTIFICATE_INDEXED)
- .findAny();
- if (appCertificateRule.isPresent()) {
- return appCertificateRule.get();
- }
-
- // Do not index when there is not package name or app certificate indexing.
- return new RuleIndexingDetails(NOT_INDEXED);
- default:
- // Having a NOT operator in the indexing messes up the indexing; e.g., deny
- // installation if app certificate is NOT X (should not be indexed with app cert
- // X). We will not keep these rules indexed.
- // Also any other type of unknown operators will not be indexed.
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-
- private static RuleIndexingDetails getIndexingDetailsForStringAtomicFormula(
- AtomicFormula.StringAtomicFormula atomicFormula) {
- switch (atomicFormula.getKey()) {
- case AtomicFormula.PACKAGE_NAME:
- return new RuleIndexingDetails(PACKAGE_NAME_INDEXED, atomicFormula.getValue());
- case AtomicFormula.APP_CERTIFICATE:
- return new RuleIndexingDetails(APP_CERTIFICATE_INDEXED, atomicFormula.getValue());
- default:
- return new RuleIndexingDetails(NOT_INDEXED);
- }
- }
-}
-
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
deleted file mode 100644
index 022b4b8cb7eb..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleMetadataSerializer.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
-import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
-
-import android.util.Xml;
-
-import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.integrity.model.RuleMetadata;
-
-import org.xmlpull.v1.XmlSerializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.charset.StandardCharsets;
-
-/** Helper class for writing rule metadata. */
-public class RuleMetadataSerializer {
- /** Serialize the rule metadata to an output stream. */
- public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
- throws IOException {
- TypedXmlSerializer xmlSerializer = Xml.resolveSerializer(outputStream);
-
- serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
- serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
-
- xmlSerializer.endDocument();
- }
-
- private static void serializeTaggedValue(TypedXmlSerializer xmlSerializer, String tag,
- String value) throws IOException {
- xmlSerializer.startTag(/* namespace= */ null, tag);
- xmlSerializer.text(value);
- xmlSerializer.endTag(/* namespace= */ null, tag);
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
deleted file mode 100644
index 2941856915a8..000000000000
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import android.content.integrity.Rule;
-
-import java.io.OutputStream;
-import java.util.List;
-import java.util.Optional;
-
-/** A helper class to serialize rules from the {@link Rule} model. */
-public interface RuleSerializer {
-
- /** Serialize rules to an output stream */
- void serialize(
- List<Rule> rules,
- Optional<Integer> formatVersion,
- OutputStream ruleFileOutputStream,
- OutputStream indexingFileOutputStream)
- throws RuleSerializeException;
-
- /** Serialize rules to a ByteArray. */
- byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
- throws RuleSerializeException;
-}
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 89555a9f1de4..c8a87994ee16 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -161,6 +161,11 @@ public class MediaSession2Record extends MediaSessionRecordImpl {
}
@Override
+ public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+ // NA as MediaSession2 doesn't support UserEngagementStates for FGS.
+ }
+
+ @Override
public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
KeyEvent ke, int sequenceId, ResultReceiver cb) {
// TODO(jaewan): Implement.
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index d752429e64f7..668ee2adbd9f 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -230,51 +230,49 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
private final Runnable mUserEngagementTimeoutExpirationRunnable =
() -> {
synchronized (mLock) {
- updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ updateUserEngagedStateIfNeededLocked(
+ /* isTimeoutExpired= */ true,
+ /* isGlobalPrioritySessionActive= */ false);
}
};
@GuardedBy("mLock")
private @UserEngagementState int mUserEngagementState = USER_DISENGAGED;
- @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARY_ENGAGED, USER_DISENGAGED})
+ @IntDef({USER_PERMANENTLY_ENGAGED, USER_TEMPORARILY_ENGAGED, USER_DISENGAGED})
@Retention(RetentionPolicy.SOURCE)
private @interface UserEngagementState {}
/**
- * Indicates that the session is active and in one of the user engaged states.
+ * Indicates that the session is {@linkplain MediaSession#isActive() active} and in one of the
+ * {@linkplain PlaybackState#isActive() active states}.
*
* @see #updateUserEngagedStateIfNeededLocked(boolean)
*/
private static final int USER_PERMANENTLY_ENGAGED = 0;
/**
- * Indicates that the session is active and in {@link PlaybackState#STATE_PAUSED} state.
+ * Indicates that the session is {@linkplain MediaSession#isActive() active} and has recently
+ * switched to one of the {@linkplain PlaybackState#isActive() inactive states}.
*
* @see #updateUserEngagedStateIfNeededLocked(boolean)
*/
- private static final int USER_TEMPORARY_ENGAGED = 1;
+ private static final int USER_TEMPORARILY_ENGAGED = 1;
/**
- * Indicates that the session is either not active or in one of the user disengaged states
+ * Indicates that the session is either not {@linkplain MediaSession#isActive() active} or in
+ * one of the {@linkplain PlaybackState#isActive() inactive states}.
*
* @see #updateUserEngagedStateIfNeededLocked(boolean)
*/
private static final int USER_DISENGAGED = 2;
/**
- * Indicates the duration of the temporary engaged states, in milliseconds.
+ * Indicates the duration of the temporary engaged state, in milliseconds.
*
- * <p>Some {@link MediaSession} states like {@link PlaybackState#STATE_PAUSED} are temporarily
- * engaged, meaning the corresponding session is only considered in an engaged state for the
- * duration of this timeout, and only if coming from an engaged state.
- *
- * <p>For example, if a session is transitioning from a user-engaged state {@link
- * PlaybackState#STATE_PLAYING} to a temporary user-engaged state {@link
- * PlaybackState#STATE_PAUSED}, then the session will be considered in a user-engaged state for
- * the duration of this timeout, starting at the transition instant. However, a temporary
- * user-engaged state is not considered user-engaged when transitioning from a non-user engaged
- * state {@link PlaybackState#STATE_STOPPED}.
+ * <p>When switching to an {@linkplain PlaybackState#isActive() inactive state}, the user is
+ * treated as temporarily engaged, meaning the corresponding session is only considered in an
+ * engaged state for the duration of this timeout, and only if coming from an engaged state.
*/
private static final int TEMP_USER_ENGAGED_TIMEOUT_MS = 600000;
@@ -598,7 +596,8 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mSessionCb.mCb.asBinder().unlinkToDeath(this, 0);
mDestroyed = true;
mPlaybackState = null;
- updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ true);
+ updateUserEngagedStateIfNeededLocked(
+ /* isTimeoutExpired= */ true, /* isGlobalPrioritySessionActive= */ false);
mHandler.post(MessageHandler.MSG_DESTROYED);
}
}
@@ -615,6 +614,24 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
mHandler.post(mUserEngagementTimeoutExpirationRunnable);
}
+ @Override
+ public void onGlobalPrioritySessionActiveChanged(boolean isGlobalPrioritySessionActive) {
+ mHandler.post(
+ () -> {
+ synchronized (mLock) {
+ if (isGlobalPrioritySessionActive) {
+ mHandler.removeCallbacks(mUserEngagementTimeoutExpirationRunnable);
+ } else {
+ if (mUserEngagementState == USER_TEMPORARILY_ENGAGED) {
+ mHandler.postDelayed(
+ mUserEngagementTimeoutExpirationRunnable,
+ TEMP_USER_ENGAGED_TIMEOUT_MS);
+ }
+ }
+ }
+ });
+ }
+
/**
* Sends media button.
*
@@ -1063,21 +1080,20 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
@GuardedBy("mLock")
- private void updateUserEngagedStateIfNeededLocked(boolean isTimeoutExpired) {
+ private void updateUserEngagedStateIfNeededLocked(
+ boolean isTimeoutExpired, boolean isGlobalPrioritySessionActive) {
if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
return;
}
int oldUserEngagedState = mUserEngagementState;
int newUserEngagedState;
- if (!isActive() || mPlaybackState == null || mDestroyed) {
+ if (!isActive() || mPlaybackState == null) {
newUserEngagedState = USER_DISENGAGED;
- } else if (isActive() && mPlaybackState.isActive()) {
+ } else if (mPlaybackState.isActive()) {
newUserEngagedState = USER_PERMANENTLY_ENGAGED;
- } else if (mPlaybackState.getState() == PlaybackState.STATE_PAUSED) {
- newUserEngagedState =
- oldUserEngagedState == USER_PERMANENTLY_ENGAGED || !isTimeoutExpired
- ? USER_TEMPORARY_ENGAGED
- : USER_DISENGAGED;
+ } else if (oldUserEngagedState == USER_PERMANENTLY_ENGAGED
+ || (oldUserEngagedState == USER_TEMPORARILY_ENGAGED && !isTimeoutExpired)) {
+ newUserEngagedState = USER_TEMPORARILY_ENGAGED;
} else {
newUserEngagedState = USER_DISENGAGED;
}
@@ -1086,7 +1102,7 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
}
mUserEngagementState = newUserEngagedState;
- if (newUserEngagedState == USER_TEMPORARY_ENGAGED) {
+ if (newUserEngagedState == USER_TEMPORARILY_ENGAGED && !isGlobalPrioritySessionActive) {
mHandler.postDelayed(
mUserEngagementTimeoutExpirationRunnable, TEMP_USER_ENGAGED_TIMEOUT_MS);
} else {
@@ -1141,9 +1157,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
.logFgsApiEnd(ActivityManager.FOREGROUND_SERVICE_API_TYPE_MEDIA_PLAYBACK,
callingUid, callingPid);
}
+ boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
synchronized (mLock) {
mIsActive = active;
- updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+ updateUserEngagedStateIfNeededLocked(
+ /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
}
long token = Binder.clearCallingIdentity();
try {
@@ -1300,9 +1318,11 @@ public class MediaSessionRecord extends MediaSessionRecordImpl implements IBinde
boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
|| (!TRANSITION_PRIORITY_STATES.contains(oldState)
&& TRANSITION_PRIORITY_STATES.contains(newState));
+ boolean isGlobalPrioritySessionActive = mService.isGlobalPrioritySessionActive();
synchronized (mLock) {
mPlaybackState = state;
- updateUserEngagedStateIfNeededLocked(/* isTimeoutExpired= */ false);
+ updateUserEngagedStateIfNeededLocked(
+ /* isTimeoutExpired= */ false, isGlobalPrioritySessionActive);
}
final long token = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
index 15f90d4fdd0e..6c3b1234935a 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -206,6 +206,10 @@ public abstract class MediaSessionRecordImpl {
*/
public abstract void expireTempEngaged();
+ /** Notifies record that the global priority session active state changed. */
+ public abstract void onGlobalPrioritySessionActiveChanged(
+ boolean isGlobalPrioritySessionActive);
+
@Override
public final boolean equals(Object o) {
if (this == o) return true;
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 1ebc856af2d8..2b29fbd9c5b5 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -362,6 +362,7 @@ public class MediaSessionService extends SystemService implements Monitor {
+ record.isActive());
}
user.pushAddressedPlayerChangedLocked();
+ mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
} else {
if (!user.mPriorityStack.contains(record)) {
Log.w(TAG, "Unknown session updated. Ignoring.");
@@ -394,11 +395,16 @@ public class MediaSessionService extends SystemService implements Monitor {
// Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
+ boolean globalPrioritySessionActiveChanged = false;
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (mGlobalPrioritySession != record) {
Log.d(TAG, "Global priority session is changed from " + mGlobalPrioritySession
+ " to " + record);
+ globalPrioritySessionActiveChanged =
+ (mGlobalPrioritySession == null && record.isActive())
+ || (mGlobalPrioritySession != null
+ && mGlobalPrioritySession.isActive() != record.isActive());
mGlobalPrioritySession = record;
if (user != null && user.mPriorityStack.contains(record)) {
// Handle the global priority session separately.
@@ -409,6 +415,30 @@ public class MediaSessionService extends SystemService implements Monitor {
}
}
}
+ if (globalPrioritySessionActiveChanged) {
+ mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
+ }
+ }
+
+ /** Returns whether the global priority session is active. */
+ boolean isGlobalPrioritySessionActive() {
+ synchronized (mLock) {
+ return isGlobalPriorityActiveLocked();
+ }
+ }
+
+ private void notifyGlobalPrioritySessionActiveChanged() {
+ if (!Flags.enableNotifyingActivityManagerWithMediaSessionStatusChange()) {
+ return;
+ }
+ synchronized (mLock) {
+ boolean isGlobalPriorityActive = isGlobalPriorityActiveLocked();
+ for (Set<MediaSessionRecordImpl> records : mUserEngagedSessionsForFgs.values()) {
+ for (MediaSessionRecordImpl record : records) {
+ record.onGlobalPrioritySessionActiveChanged(isGlobalPriorityActive);
+ }
+ }
+ }
}
private List<MediaSessionRecord> getActiveSessionsLocked(int userId) {
@@ -646,8 +676,11 @@ public class MediaSessionService extends SystemService implements Monitor {
if (mGlobalPrioritySession == session) {
mGlobalPrioritySession = null;
- if (session.isActive() && user != null) {
- user.pushAddressedPlayerChangedLocked();
+ if (session.isActive()) {
+ if (user != null) {
+ user.pushAddressedPlayerChangedLocked();
+ }
+ mHandler.post(this::notifyGlobalPrioritySessionActiveChanged);
}
} else {
if (user != null) {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 122836e19d58..93482e769a2b 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -21,9 +21,6 @@ import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
import static android.content.Context.DEVICE_POLICY_SERVICE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
-import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-import static android.content.pm.PackageManager.MATCH_INSTANT;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
@@ -109,8 +106,7 @@ abstract public class ManagedServices {
protected final String TAG = getClass().getSimpleName().replace('$', '.');
protected final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- protected static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
- protected static final int ON_BINDING_DIED_REBIND_MSG = 1234;
+ private static final int ON_BINDING_DIED_REBIND_DELAY_MS = 10000;
protected static final String ENABLED_SERVICES_SEPARATOR = ":";
private static final String DB_VERSION_1 = "1";
private static final String DB_VERSION_2 = "2";
@@ -879,21 +875,7 @@ abstract public class ManagedServices {
String approvedItem = getApprovedValue(pkgOrComponent);
if (approvedItem != null) {
- final ComponentName component = ComponentName.unflattenFromString(approvedItem);
if (enabled) {
- if (Flags.notificationNlsRebind()) {
- if (component != null && !isValidService(component, userId)) {
- // Only fail if package is available
- // If not, it will be validated again in onPackagesChanged
- final PackageManager pm = mContext.getPackageManager();
- if (pm.isPackageAvailable(component.getPackageName())) {
- Slog.w(TAG, "Skip allowing " + mConfig.caption
- + " " + pkgOrComponent + " (userSet: " + userSet
- + ") for invalid service");
- return;
- }
- }
- }
approved.add(approvedItem);
} else {
approved.remove(approvedItem);
@@ -991,7 +973,7 @@ abstract public class ManagedServices {
|| isPackageOrComponentAllowed(component.getPackageName(), userId))) {
return false;
}
- return isValidService(component, userId);
+ return componentHasBindPermission(component, userId);
}
private boolean componentHasBindPermission(ComponentName component, int userId) {
@@ -1238,21 +1220,12 @@ abstract public class ManagedServices {
if (!TextUtils.isEmpty(packageName)) {
queryIntent.setPackage(packageName);
}
-
- if (Flags.notificationNlsRebind()) {
- // Expand the package query
- extraFlags |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
- extraFlags |= MATCH_INSTANT;
- }
-
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
queryIntent,
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA | extraFlags,
userId);
- if (DEBUG) {
- Slog.v(TAG, mConfig.serviceInterface + " pkg: " + packageName + " services: "
- + installedServices);
- }
+ if (DEBUG)
+ Slog.v(TAG, mConfig.serviceInterface + " services: " + installedServices);
if (installedServices != null) {
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
@@ -1352,12 +1325,11 @@ abstract public class ManagedServices {
if (TextUtils.equals(getPackageName(approvedPackageOrComponent), packageName)) {
final ComponentName component = ComponentName.unflattenFromString(
approvedPackageOrComponent);
- if (component != null && !isValidService(component, userId)) {
+ if (component != null && !componentHasBindPermission(component, userId)) {
approved.removeAt(j);
if (DEBUG) {
Slog.v(TAG, "Removing " + approvedPackageOrComponent
- + " from approved list; no bind permission or "
- + "service interface filter found "
+ + " from approved list; no bind permission found "
+ mConfig.bindPermission);
}
}
@@ -1376,15 +1348,6 @@ abstract public class ManagedServices {
}
}
- protected boolean isValidService(ComponentName component, int userId) {
- if (Flags.notificationNlsRebind()) {
- return componentHasBindPermission(component, userId) && queryPackageForServices(
- component.getPackageName(), userId).contains(component);
- } else {
- return componentHasBindPermission(component, userId);
- }
- }
-
protected boolean isValidEntry(String packageOrComponent, int userId) {
return hasMatchingServices(packageOrComponent, userId);
}
@@ -1542,27 +1505,23 @@ abstract public class ManagedServices {
* Called when user switched to unbind all services from other users.
*/
@VisibleForTesting
- void unbindOtherUserServices(int switchedToUser) {
+ void unbindOtherUserServices(int currentUser) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindOtherUserServices_current" + switchedToUser);
- unbindServicesImpl(switchedToUser, true /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindOtherUserServices_current" + currentUser);
+ unbindServicesImpl(currentUser, true /* allExceptUser */);
t.traceEnd();
}
- void unbindUserServices(int removedUser) {
+ void unbindUserServices(int user) {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
- t.traceBegin("ManagedServices.unbindUserServices" + removedUser);
- unbindServicesImpl(removedUser, false /* allExceptUser */);
+ t.traceBegin("ManagedServices.unbindUserServices" + user);
+ unbindServicesImpl(user, false /* allExceptUser */);
t.traceEnd();
}
void unbindServicesImpl(int user, boolean allExceptUser) {
final SparseArray<Set<ComponentName>> componentsToUnbind = new SparseArray<>();
synchronized (mMutex) {
- if (Flags.notificationNlsRebind()) {
- // Remove enqueued rebinds to avoid rebinding services for a switched user
- mHandler.removeMessages(ON_BINDING_DIED_REBIND_MSG);
- }
final Set<ManagedServiceInfo> removableBoundServices = getRemovableConnectedServices();
for (ManagedServiceInfo info : removableBoundServices) {
if ((allExceptUser && (info.userid != user))
@@ -1757,7 +1716,6 @@ abstract public class ManagedServices {
mServicesRebinding.add(servicesBindingTag);
mHandler.postDelayed(() ->
reregisterService(name, userid),
- ON_BINDING_DIED_REBIND_MSG,
ON_BINDING_DIED_REBIND_DELAY_MS);
} else {
Slog.v(TAG, getCaption() + " not rebinding in user " + userid
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 2c45fc89ff0b..dd6c59fbea18 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -82,6 +82,8 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BA
import static android.app.NotificationManager.zenModeFromInterruptionFilter;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
+import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
+import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
@@ -162,8 +164,6 @@ import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.server.am.PendingIntentRecord.FLAG_ACTIVITY_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_BROADCAST_SENDER;
import static com.android.server.am.PendingIntentRecord.FLAG_SERVICE_SENDER;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.ERROR_XML_PARSING;
import static com.android.server.notification.Flags.expireBitmaps;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_ANIM_BUFFER;
import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
@@ -7770,10 +7770,11 @@ public class NotificationManagerService extends SystemService {
// Make Notification silent
r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE;
- // Repost
+ // Repost as the original app (even if it was posted by a delegate originally
+ // because the delegate may now be revoked)
enqueueNotificationInternal(r.getSbn().getPackageName(),
- r.getSbn().getOpPkg(), r.getSbn().getUid(),
- r.getSbn().getInitialPid(), r.getSbn().getTag(),
+ r.getSbn().getPackageName(), r.getSbn().getUid(),
+ MY_PID, r.getSbn().getTag(),
r.getSbn().getId(), r.getNotification(),
r.getSbn().getUserId(), /* postSilently= */ true,
/* byForegroundService= */ false,
@@ -8012,7 +8013,6 @@ public class NotificationManagerService extends SystemService {
r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
boolean isImportanceFixed = mPermissionHelper.isPermissionFixed(pkg, userId);
r.setImportanceFixed(isImportanceFixed);
-
if (notification.isFgsOrUij()) {
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
|| !channel.isUserVisibleTaskShown())
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index c479acfd6228..f79d9ef174ea 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -194,13 +194,3 @@ flag {
description: "Enables sound uri with vibration source in notification channel"
bug: "351975435"
}
-
-flag {
- name: "notification_nls_rebind"
- namespace: "systemui"
- description: "Check for NLS service intent filter when rebinding services"
- bug: "347674739"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 20cca969dade..af2bb17fd0e6 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -404,9 +404,11 @@ public class BackgroundInstallControlService extends SystemService {
void handlePackageRemove(String packageName, int userId) {
initBackgroundInstalledPackages();
+ if (mBackgroundInstalledPackages.contains(userId, packageName)) {
+ mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
+ }
mBackgroundInstalledPackages.remove(userId, packageName);
writeBackgroundInstalledPackagesToDisk();
- mCallbackHelper.notifyAllCallbacks(userId, packageName, INSTALL_EVENT_TYPE_UNINSTALL);
}
void handleUsageEvent(UsageEvents.Event event, int userId) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 355184e1c758..d9e76966892c 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -85,6 +85,7 @@ import static com.android.server.pm.PackageManagerService.SCAN_NO_DEX;
import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
import static com.android.server.pm.PackageManagerService.SCAN_UPDATE_SIGNATURE;
import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerService.WATCHDOG_TIMEOUT;
import static com.android.server.pm.PackageManagerServiceUtils.comparePackageSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
import static com.android.server.pm.PackageManagerServiceUtils.compressedFileExists;
@@ -133,9 +134,11 @@ import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Message;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
@@ -174,7 +177,6 @@ import com.android.internal.util.CollectionUtils;
import com.android.server.EventLogTags;
import com.android.server.SystemConfig;
import com.android.server.criticalevents.CriticalEventLog;
-import com.android.server.pm.dex.ArtManagerService;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.parsing.PackageCacher;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
@@ -210,6 +212,10 @@ import java.util.concurrent.ExecutorService;
final class InstallPackageHelper {
+ // One minute over PM WATCHDOG_TIMEOUT
+ private static final long WAKELOCK_TIMEOUT_MS = WATCHDOG_TIMEOUT + 1000 * 60;
+ private static final String INSTALLER_WAKE_LOCK_TAG = "installer:packages";
+
private final PackageManagerService mPm;
private final AppDataHelper mAppDataHelper;
private final BroadcastHelper mBroadcastHelper;
@@ -218,14 +224,16 @@ final class InstallPackageHelper {
private final IncrementalManager mIncrementalManager;
private final ApexManager mApexManager;
private final DexManager mDexManager;
- private final ArtManagerService mArtManagerService;
private final Context mContext;
- private final PackageDexOptimizer mPackageDexOptimizer;
private final PackageAbiHelper mPackageAbiHelper;
private final SharedLibrariesImpl mSharedLibraries;
private final PackageManagerServiceInjector mInjector;
private final UpdateOwnershipHelper mUpdateOwnershipHelper;
+ private final Object mInternalLock = new Object();
+ @GuardedBy("mInternalLock")
+ private PowerManager.WakeLock mInstallingWakeLock;
+
// TODO(b/198166813): remove PMS dependency
InstallPackageHelper(PackageManagerService pm,
AppDataHelper appDataHelper,
@@ -241,9 +249,7 @@ final class InstallPackageHelper {
mIncrementalManager = pm.mInjector.getIncrementalManager();
mApexManager = pm.mInjector.getApexManager();
mDexManager = pm.mInjector.getDexManager();
- mArtManagerService = pm.mInjector.getArtManagerService();
mContext = pm.mInjector.getContext();
- mPackageDexOptimizer = pm.mInjector.getPackageDexOptimizer();
mPackageAbiHelper = pm.mInjector.getAbiHelper();
mSharedLibraries = pm.mInjector.getSharedLibrariesImpl();
mUpdateOwnershipHelper = pm.mInjector.getUpdateOwnershipHelper();
@@ -1013,6 +1019,7 @@ final class InstallPackageHelper {
boolean success = false;
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
+ final long acquireTime = acquireWakeLock(requests.size());
try {
CriticalEventLog.getInstance().logInstallPackagesStarted();
if (prepareInstallPackages(requests)
@@ -1033,6 +1040,46 @@ final class InstallPackageHelper {
} finally {
completeInstallProcess(requests, createdAppId, success);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+ releaseWakeLock(acquireTime, requests.size());
+ }
+ }
+
+ private long acquireWakeLock(int count) {
+ if (!mPm.isSystemReady()) {
+ return -1;
+ }
+ synchronized (mInternalLock) {
+ if (mInstallingWakeLock == null) {
+ PowerManager pwm = mContext.getSystemService(PowerManager.class);
+ if (pwm != null) {
+ mInstallingWakeLock = pwm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ INSTALLER_WAKE_LOCK_TAG);
+ } else {
+ Slog.w(TAG, "Unable to obtain power manager while obtaining wake lock");
+ return -1;
+ }
+ }
+
+ mInstallingWakeLock.acquire(WAKELOCK_TIMEOUT_MS * count);
+ return SystemClock.elapsedRealtime();
+ }
+ }
+
+ private void releaseWakeLock(final long acquireTime, int count) {
+ if (acquireTime < 0) {
+ return;
+ }
+ synchronized (mInternalLock) {
+ try {
+ if (mInstallingWakeLock == null) {
+ return;
+ }
+ if (mInstallingWakeLock.isHeld()) {
+ mInstallingWakeLock.release();
+ }
+ } catch (RuntimeException e) {
+ Slog.wtf(TAG, "Error while releasing installer lock", e);
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 7ecfe7f64ffe..498659427a21 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -341,6 +341,10 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TRON_USER_CREATED = "users_user_created";
private static final String TRON_DEMO_CREATED = "users_demo_created";
+ // The boot user strategy for HSUM.
+ private static final int BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER = 0;
+ private static final int BOOT_TO_HSU_FOR_PROVISIONED_DEVICE = 1;
+
private final Context mContext;
private final PackageManagerService mPm;
@@ -1391,37 +1395,77 @@ public class UserManagerService extends IUserManager.Stub {
}
if (isHeadlessSystemUserMode()) {
- if (mContext.getResources()
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser)) {
- return UserHandle.USER_SYSTEM;
- }
- // Return the previous foreground user, if there is one.
- final int previousUser = getPreviousFullUserToEnterForeground();
- if (previousUser != UserHandle.USER_NULL) {
- Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
- return previousUser;
- }
- // No previous user. Return the first switchable user if there is one.
- synchronized (mUsersLock) {
- final int userSize = mUsers.size();
- for (int i = 0; i < userSize; i++) {
- final UserData userData = mUsers.valueAt(i);
- if (userData.info.supportsSwitchToByUser()) {
- int firstSwitchable = userData.info.id;
- Slogf.i(LOG_TAG,
- "Boot user is first switchable user %d", firstSwitchable);
- return firstSwitchable;
- }
- }
+ final int bootStrategy = mContext.getResources()
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+ switch (bootStrategy) {
+ case BOOT_TO_PREVIOUS_OR_FIRST_SWITCHABLE_USER:
+ return getPreviousOrFirstSwitchableUser();
+ case BOOT_TO_HSU_FOR_PROVISIONED_DEVICE:
+ return getBootUserBasedOnProvisioning();
+ default:
+ Slogf.w(LOG_TAG, "Unknown HSUM boot strategy: %d", bootStrategy);
+ return getPreviousOrFirstSwitchableUser();
}
- // No switchable users found. Uh oh!
- throw new UserManager.CheckedUserOperationException(
- "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
}
// Not HSUM, return system user.
return UserHandle.USER_SYSTEM;
}
+ private @UserIdInt int getBootUserBasedOnProvisioning()
+ throws UserManager.CheckedUserOperationException {
+ final boolean provisioned = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+ if (provisioned) {
+ return UserHandle.USER_SYSTEM;
+ } else {
+ final int firstSwitchableFullUser = getFirstSwitchableUser(true);
+ if (firstSwitchableFullUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable full user %d",
+ firstSwitchableFullUser);
+ return firstSwitchableFullUser;
+ }
+ // No switchable full user found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable full user found", USER_OPERATION_ERROR_UNKNOWN);
+ }
+ }
+
+ private @UserIdInt int getPreviousOrFirstSwitchableUser()
+ throws UserManager.CheckedUserOperationException {
+ // Return the previous foreground user, if there is one.
+ final int previousUser = getPreviousFullUserToEnterForeground();
+ if (previousUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG, "Boot user is previous user %d", previousUser);
+ return previousUser;
+ }
+ // No previous user. Return the first switchable user if there is one.
+ final int firstSwitchableUser = getFirstSwitchableUser(false);
+ if (firstSwitchableUser != UserHandle.USER_NULL) {
+ Slogf.i(LOG_TAG,
+ "Boot user is first switchable user %d", firstSwitchableUser);
+ return firstSwitchableUser;
+ }
+ // No switchable users found. Uh oh!
+ throw new UserManager.CheckedUserOperationException(
+ "No switchable users found", USER_OPERATION_ERROR_UNKNOWN);
+ }
+
+ private @UserIdInt int getFirstSwitchableUser(boolean fullUserOnly) {
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ for (int i = 0; i < userSize; i++) {
+ final UserData userData = mUsers.valueAt(i);
+ if (userData.info.supportsSwitchToByUser() &&
+ (!fullUserOnly || userData.info.isFull())) {
+ int firstSwitchable = userData.info.id;
+ return firstSwitchable;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public int getPreviousFullUserToEnterForeground() {
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 5fc3e332b95c..24933cab2a32 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1015,7 +1015,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
permission, attributionSource, message, forDataDelivery, startDataDelivery,
fromDatasource, attributedOp);
// Finish any started op if some step in the attribution chain failed.
- if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED) {
+ if (startDataDelivery && result != PermissionChecker.PERMISSION_GRANTED
+ && result != PermissionChecker.PERMISSION_SOFT_DENIED) {
if (attributedOp == AppOpsManager.OP_NONE) {
finishDataDelivery(AppOpsManager.permissionToOpCode(permission),
attributionSource.asState(), fromDatasource);
@@ -1244,6 +1245,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
final boolean hasChain = attributionChainId != ATTRIBUTION_CHAIN_ID_NONE;
AttributionSource current = attributionSource;
AttributionSource next = null;
+ AttributionSource prev = null;
// We consider the chain trusted if the start node has UPDATE_APP_OPS_STATS, and
// every attributionSource in the chain is registered with the system.
final boolean isChainStartTrusted = !hasChain || checkPermission(context,
@@ -1310,8 +1312,21 @@ public class PermissionManagerService extends IPermissionManager.Stub {
selfAccess, singleReceiverFromDatasource, attributedOp,
proxyAttributionFlags, proxiedAttributionFlags, attributionChainId);
- switch (opMode) {
- case AppOpsManager.MODE_ERRORED: {
+ if (opMode != AppOpsManager.MODE_ALLOWED) {
+ // Current failed the perm check, so if we are part-way through an attr chain,
+ // we need to clean up the already started proxy op higher up the chain. Note,
+ // proxy ops are verified two by two, which means we have to clear the 2nd next
+ // from the previous iteration (since it is actually curr.next which failed
+ // to pass the perm check).
+ if (prev != null) {
+ final var cutAttrSourceState = prev.asState();
+ if (cutAttrSourceState.next.length > 0) {
+ cutAttrSourceState.next[0].next = new AttributionSourceState[0];
+ }
+ finishDataDelivery(context, attributedOp,
+ cutAttrSourceState, fromDatasource);
+ }
+ if (opMode == AppOpsManager.MODE_ERRORED) {
if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as op"
+ " mode is MODE_ERRORED. Permission check was requested for: "
@@ -1319,8 +1334,7 @@ public class PermissionManagerService extends IPermissionManager.Stub {
+ current);
}
return PermissionChecker.PERMISSION_HARD_DENIED;
- }
- case AppOpsManager.MODE_IGNORED: {
+ } else {
return PermissionChecker.PERMISSION_SOFT_DENIED;
}
}
@@ -1335,6 +1349,8 @@ public class PermissionManagerService extends IPermissionManager.Stub {
return PermissionChecker.PERMISSION_GRANTED;
}
+ // an attribution we have already possibly started an op for
+ prev = current;
current = next;
}
}
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index a1236e533beb..4f67318faddb 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -32,6 +32,7 @@ import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.graphics.drawable.Icon;
import android.hardware.input.AppLaunchData;
+import android.hardware.input.InputGestureData;
import android.hardware.input.KeyGestureEvent;
import android.os.Handler;
import android.os.RemoteException;
@@ -769,6 +770,30 @@ public class ModifierShortcutManager {
shortcuts);
}
+ /**
+ * @return a {@link KeyboardShortcutGroup} containing the application launch keyboard
+ * shortcuts based on provided list of shortcut data.
+ */
+ public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId,
+ List<InputGestureData> shortcutData) {
+ List<KeyboardShortcutInfo> shortcuts = new ArrayList<>();
+ KeyCharacterMap kcm = KeyCharacterMap.load(deviceId);
+ for (InputGestureData data : shortcutData) {
+ if (data.getTrigger() instanceof InputGestureData.KeyTrigger trigger) {
+ KeyboardShortcutInfo info = shortcutInfoFromIntent(
+ kcm.getDisplayLabel(trigger.getKeycode()),
+ getIntentFromAppLaunchData(data.getAction().appLaunchData()),
+ (trigger.getModifierState() & KeyEvent.META_SHIFT_ON) != 0);
+ if (info != null) {
+ shortcuts.add(info);
+ }
+ }
+ }
+ return new KeyboardShortcutGroup(
+ mContext.getString(R.string.keyboard_shortcut_group_applications),
+ shortcuts);
+ }
+
private Intent getIntentFromAppLaunchData(@NonNull AppLaunchData data) {
Context context = mContext.createContextAsUser(mCurrentUser, 0);
synchronized (mAppIntentCache) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 2893430572d8..fc24e62de04e 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -3414,6 +3414,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
+ if (useKeyGestureEventHandler()) {
+ return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId,
+ mInputManager.getAppLaunchBookmarks());
+ }
return mModifierShortcutManager.getApplicationLaunchKeyboardShortcuts(deviceId);
}
@@ -4004,14 +4008,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private boolean interceptSystemKeysAndShortcutsNew(IBinder focusedToken, KeyEvent event) {
final int keyCode = event.getKeyCode();
final int metaState = event.getMetaState();
- final boolean keyguardOn = keyguardOn();
- if (isUserSetupComplete() && !keyguardOn) {
- if (mModifierShortcutManager.interceptKey(event)) {
- dismissKeyboardShortcutsMenu();
- return true;
- }
- }
switch (keyCode) {
case KeyEvent.KEYCODE_HOME:
return handleHomeShortcuts(focusedToken, event);
@@ -4753,6 +4750,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
public void registerShortcutKey(long shortcutCode, IShortcutService shortcutService)
throws RemoteException {
synchronized (mLock) {
+ if (useKeyGestureEventHandler()) {
+ mInputManagerInternal.registerShortcutKey(shortcutCode, shortcutService);
+ return;
+ }
mModifierShortcutManager.registerShortcutKey(shortcutCode, shortcutService);
}
}
diff --git a/services/core/java/com/android/server/power/PowerGroup.java b/services/core/java/com/android/server/power/PowerGroup.java
index a928814c7909..01a2045df426 100644
--- a/services/core/java/com/android/server/power/PowerGroup.java
+++ b/services/core/java/com/android/server/power/PowerGroup.java
@@ -42,6 +42,8 @@ import android.view.Display;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.power.feature.PowerManagerFlags;
/**
@@ -56,6 +58,11 @@ public class PowerGroup {
private static final String TAG = PowerGroup.class.getSimpleName();
private static final boolean DEBUG = false;
+ /**
+ * Indicates that the default dim/sleep timeouts should be used.
+ */
+ private static final long INVALID_TIMEOUT = -1;
+
@VisibleForTesting
final DisplayPowerRequest mDisplayPowerRequest = new DisplayPowerRequest();
private final PowerGroupListener mWakefulnessListener;
@@ -91,6 +98,9 @@ public class PowerGroup {
private @PowerManager.GoToSleepReason int mLastSleepReason =
PowerManager.GO_TO_SLEEP_REASON_UNKNOWN;
+ private final long mDimDuration;
+ private final long mScreenOffTimeout;
+
PowerGroup(int groupId, PowerGroupListener wakefulnessListener, Notifier notifier,
DisplayManagerInternal displayManagerInternal, int wakefulness, boolean ready,
boolean supportsSandman, long eventTime, PowerManagerFlags featureFlags) {
@@ -104,6 +114,30 @@ public class PowerGroup {
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
mFeatureFlags = featureFlags;
+
+ long dimDuration = INVALID_TIMEOUT;
+ long screenOffTimeout = INVALID_TIMEOUT;
+ if (android.companion.virtualdevice.flags.Flags.deviceAwareDisplayPower()
+ && mGroupId != Display.DEFAULT_DISPLAY_GROUP) {
+ VirtualDeviceManagerInternal vdm =
+ LocalServices.getService(VirtualDeviceManagerInternal.class);
+ if (vdm != null) {
+ int[] displayIds = mDisplayManagerInternal.getDisplayIdsForGroup(mGroupId);
+ if (displayIds != null && displayIds.length > 0) {
+ int deviceId = vdm.getDeviceIdForDisplayId(displayIds[0]);
+ if (vdm.isValidVirtualDeviceId(deviceId)) {
+ dimDuration = vdm.getDimDurationMillisForDeviceId(deviceId);
+ screenOffTimeout = vdm.getScreenOffTimeoutMillisForDeviceId(deviceId);
+ if (dimDuration > 0 && dimDuration > screenOffTimeout) {
+ // If the dim duration is set, cap it to the screen off timeout.
+ dimDuration = screenOffTimeout;
+ }
+ }
+ }
+ }
+ }
+ mDimDuration = dimDuration;
+ mScreenOffTimeout = screenOffTimeout;
}
PowerGroup(int wakefulness, PowerGroupListener wakefulnessListener, Notifier notifier,
@@ -119,6 +153,16 @@ public class PowerGroup {
mLastWakeTime = eventTime;
mLastSleepTime = eventTime;
mFeatureFlags = featureFlags;
+ mDimDuration = INVALID_TIMEOUT;
+ mScreenOffTimeout = INVALID_TIMEOUT;
+ }
+
+ long getScreenOffTimeoutOverrideLocked(long defaultScreenOffTimeout) {
+ return mScreenOffTimeout == INVALID_TIMEOUT ? defaultScreenOffTimeout : mScreenOffTimeout;
+ }
+
+ long getScreenDimDurationOverrideLocked(long defaultScreenDimDuration) {
+ return mDimDuration == INVALID_TIMEOUT ? defaultScreenDimDuration : mDimDuration;
}
long getLastWakeTimeLocked() {
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 3a5afacd0977..0acfe92f578d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -2971,8 +2971,8 @@ public final class PowerManagerService extends SystemService
mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
final long attentiveTimeout = getAttentiveTimeoutLocked();
- final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
- final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
+ final long defaultSleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
+ final long defaultScreenOffTimeout = getScreenOffTimeoutLocked(defaultSleepTimeout,
attentiveTimeout);
final long defaultScreenDimDuration = getScreenDimDurationLocked(defaultScreenOffTimeout);
@@ -2985,13 +2985,25 @@ public final class PowerManagerService extends SystemService
final PowerGroup powerGroup = mPowerGroups.valueAt(idx);
final int wakefulness = powerGroup.getWakefulnessLocked();
- // The default display screen timeout could be overridden by policy.
+ // The timeouts could be overridden by the power group policy.
long screenOffTimeout = defaultScreenOffTimeout;
long screenDimDuration = defaultScreenDimDuration;
+ long sleepTimeout = defaultSleepTimeout;
+ // TODO(b/376211497): Consolidate the timeout logic for all power groups.
if (powerGroup.getGroupId() == Display.DEFAULT_DISPLAY_GROUP) {
screenOffTimeout =
- getScreenOffTimeoutOverrideLocked(screenOffTimeout, screenDimDuration);
+ getDefaultGroupScreenOffTimeoutOverrideLocked(screenOffTimeout,
+ screenDimDuration);
screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
+ } else {
+ screenOffTimeout = powerGroup.getScreenOffTimeoutOverrideLocked(screenOffTimeout);
+ screenDimDuration =
+ powerGroup.getScreenDimDurationOverrideLocked(screenDimDuration);
+ if (sleepTimeout > 0 && screenOffTimeout > 0) {
+ // If both sleep and screen off timeouts are set, make sure that the sleep
+ // timeout is not smaller than the screen off one.
+ sleepTimeout = Math.max(sleepTimeout, screenOffTimeout);
+ }
}
if (wakefulness != WAKEFULNESS_ASLEEP) {
@@ -3273,7 +3285,8 @@ public final class PowerManagerService extends SystemService
@VisibleForTesting
@GuardedBy("mLock")
- long getScreenOffTimeoutOverrideLocked(long screenOffTimeout, long screenDimDuration) {
+ long getDefaultGroupScreenOffTimeoutOverrideLocked(long screenOffTimeout,
+ long screenDimDuration) {
long shortestScreenOffTimeout = screenOffTimeout;
if (mScreenTimeoutOverridePolicy != null) {
shortestScreenOffTimeout =
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 48174a6bad11..940a5091a4be 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -809,17 +809,16 @@ public class BatteryStatsImpl extends BatteryStats {
mKernelSingleUidTimeReader.addDelta(uid, onBatteryScreenOffCounter, elapsedRealtimeMs);
if (u.mChildUids != null) {
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ long[] delta = getCpuTimeInFreqContainer();
int childUidCount = u.mChildUids.size();
for (int j = childUidCount - 1; j >= 0; --j) {
LongArrayMultiStateCounter cpuTimeInFreqCounter =
u.mChildUids.valueAt(j).cpuTimeInFreqCounter;
if (cpuTimeInFreqCounter != null) {
mKernelSingleUidTimeReader.addDelta(u.mChildUids.keyAt(j),
- cpuTimeInFreqCounter, elapsedRealtimeMs, deltaContainer);
- onBatteryCounter.addCounts(deltaContainer);
- onBatteryScreenOffCounter.addCounts(deltaContainer);
+ cpuTimeInFreqCounter, elapsedRealtimeMs, delta);
+ onBatteryCounter.addCounts(delta);
+ onBatteryScreenOffCounter.addCounts(delta);
}
}
}
@@ -890,8 +889,7 @@ public class BatteryStatsImpl extends BatteryStats {
if (childUid != null) {
final LongArrayMultiStateCounter counter = childUid.cpuTimeInFreqCounter;
if (counter != null) {
- final LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- getCpuTimeInFreqContainer();
+ final long[] deltaContainer = getCpuTimeInFreqContainer();
mKernelSingleUidTimeReader.addDelta(uid, counter, elapsedRealtimeMs,
deltaContainer);
onBatteryCounter.addCounts(deltaContainer);
@@ -1741,7 +1739,7 @@ public class BatteryStatsImpl extends BatteryStats {
private long mBatteryTimeToFullSeconds = -1;
- private LongArrayMultiStateCounter.LongArrayContainer mTmpCpuTimeInFreq;
+ private long[] mTmpCpuTimeInFreq;
/**
* Times spent by the system server threads handling incoming binder requests.
@@ -10956,9 +10954,7 @@ public class BatteryStatsImpl extends BatteryStats {
// Set initial values to all 0. This is a child UID and we want to include
// the entirety of its CPU time-in-freq stats into the parent's stats.
- cpuTimeInFreqCounter.updateValues(
- new LongArrayMultiStateCounter.LongArrayContainer(cpuFreqCount),
- timestampMs);
+ cpuTimeInFreqCounter.updateValues(new long[cpuFreqCount], timestampMs);
} else {
cpuTimeInFreqCounter = null;
}
@@ -11361,11 +11357,9 @@ public class BatteryStatsImpl extends BatteryStats {
}
@GuardedBy("this")
- private LongArrayMultiStateCounter.LongArrayContainer getCpuTimeInFreqContainer() {
+ private long[] getCpuTimeInFreqContainer() {
if (mTmpCpuTimeInFreq == null) {
- mTmpCpuTimeInFreq =
- new LongArrayMultiStateCounter.LongArrayContainer(
- mCpuScalingPolicies.getScalingStepCount());
+ mTmpCpuTimeInFreq = new long[mCpuScalingPolicies.getScalingStepCount()];
}
return mTmpCpuTimeInFreq;
}
diff --git a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
index e798bc487031..3f7fcee5bb9a 100644
--- a/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
+++ b/services/core/java/com/android/server/stats/pull/netstats/NetworkStatsAccumulator.java
@@ -19,6 +19,7 @@ package com.android.server.stats.pull.netstats;
import android.annotation.NonNull;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
+import android.util.Log;
import java.util.Objects;
@@ -33,6 +34,7 @@ import java.util.Objects;
*/
public class NetworkStatsAccumulator {
+ private static final String TAG = "NetworkStatsAccumulator";
private final NetworkTemplate mTemplate;
private final boolean mWithTags;
private final long mBucketDurationMillis;
@@ -57,8 +59,9 @@ public class NetworkStatsAccumulator {
@NonNull
public NetworkStats queryStats(long currentTimeMillis,
@NonNull StatsQueryFunction queryFunction) {
- maybeExpandSnapshot(currentTimeMillis, queryFunction);
- return snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ NetworkStats completeStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ maybeExpandSnapshot(currentTimeMillis, completeStats, queryFunction);
+ return completeStats;
}
/**
@@ -72,15 +75,28 @@ public class NetworkStatsAccumulator {
* Expands the internal cumulative stats snapshot, if possible, by querying NetworkStats.
*/
private void maybeExpandSnapshot(long currentTimeMillis,
+ NetworkStats completeStatsUntilCurrentTime,
@NonNull StatsQueryFunction queryFunction) {
// Update snapshot only if it is possible to expand it by at least one full bucket, and only
// if the new snapshot's end is not in the active bucket.
long newEndTimeMillis = currentTimeMillis - mBucketDurationMillis;
if (newEndTimeMillis - mSnapshotEndTimeMillis > mBucketDurationMillis) {
- NetworkStats extraStats = queryFunction.queryNetworkStats(mTemplate, mWithTags,
- mSnapshotEndTimeMillis, newEndTimeMillis);
+ Log.v(TAG,
+ "Expanding snapshot (mTemplate=" + mTemplate + ", mWithTags=" + mWithTags
+ + ") from " + mSnapshotEndTimeMillis + " to " + newEndTimeMillis
+ + " at " + currentTimeMillis);
+ NetworkStats extraStats = queryFunction.queryNetworkStats(
+ mTemplate, mWithTags, mSnapshotEndTimeMillis, newEndTimeMillis);
mSnapshot = mSnapshot.add(extraStats);
mSnapshotEndTimeMillis = newEndTimeMillis;
+
+ // NetworkStats queries interpolate historical data using integers maths, which makes
+ // queries non-transitive: Query(t0, t1) + Query(t1, t2) <= Query(t0, t2).
+ // Compute interpolation data loss from moving the snapshot's end-point, and add it to
+ // the snapshot to avoid under-counting.
+ NetworkStats newStats = snapshotPlusFollowingStats(currentTimeMillis, queryFunction);
+ NetworkStats interpolationLoss = completeStatsUntilCurrentTime.subtract(newStats);
+ mSnapshot = mSnapshot.add(interpolationLoss);
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 69643002750b..73ae51c6e64a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -707,9 +707,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
private boolean mOccludesParent;
- /** Whether the activity have style floating */
- private boolean mStyleFloating;
-
/**
* Unlike {@link #mOccludesParent} which can be changed at runtime. This is a static attribute
* from the style of activity. Because we don't want {@link WindowContainer#getOrientation()}
@@ -791,10 +788,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false.
private boolean mIsEligibleForFixedOrientationLetterbox;
- // activity is not displayed?
- // TODO: rename to mNoDisplay
- @VisibleForTesting
- boolean noDisplay;
+ /**
+ * Whether the activity is to be displayed. See {@link android.R.attr#windowNoDisplay}.
+ */
+ private boolean mNoDisplay;
final boolean mShowForAllUsers;
// TODO: Make this final
int mTargetSdk;
@@ -1178,7 +1175,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
pw.print(" inHistory="); pw.print(inHistory);
pw.print(" idle="); pw.println(idle);
pw.print(prefix); pw.print("occludesParent="); pw.print(occludesParent());
- pw.print(" noDisplay="); pw.print(noDisplay);
+ pw.print(" mNoDisplay="); pw.print(mNoDisplay);
pw.print(" immersive="); pw.print(immersive);
pw.print(" launchMode="); pw.println(launchMode);
pw.print(prefix); pw.print("mActivityType=");
@@ -2011,20 +2008,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (ent != null) {
final boolean styleTranslucent = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsTranslucent, false);
- mStyleFloating = ent.array.getBoolean(
+ final boolean styleFloating = ent.array.getBoolean(
com.android.internal.R.styleable.Window_windowIsFloating, false);
- mOccludesParent = !(styleTranslucent || mStyleFloating)
+ mOccludesParent = !(styleTranslucent || styleFloating)
// This style is propagated to the main window attributes with
// FLAG_SHOW_WALLPAPER from PhoneWindow#generateLayout.
|| ent.array.getBoolean(R.styleable.Window_windowShowWallpaper, false);
mStyleFillsParent = mOccludesParent;
- noDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
+ mNoDisplay = ent.array.getBoolean(R.styleable.Window_windowNoDisplay, false);
mOptOutEdgeToEdge = ent.array.getBoolean(
R.styleable.Window_windowOptOutEdgeToEdgeEnforcement, false);
} else {
- mStyleFloating = false;
mStyleFillsParent = mOccludesParent = true;
- noDisplay = false;
+ mNoDisplay = false;
mOptOutEdgeToEdge = false;
}
@@ -3091,8 +3087,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return occludesParent(true /* includingFinishing */);
}
- boolean isStyleFloating() {
- return mStyleFloating;
+ boolean isNoDisplay() {
+ return mNoDisplay;
+ }
+
+ /**
+ * Exposed only for testing and should not be used to modify value of {@link #mNoDisplay}.
+ */
+ @VisibleForTesting
+ void setIsNoDisplay(boolean isNoDisplay) {
+ mNoDisplay = isNoDisplay;
}
/** Returns true if this activity is not finishing, is opaque and fills the entire space of
@@ -3192,6 +3196,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mWmService.mConstants.isPackageOptOutIgnoreActivityOrientationRequest(packageName)) {
return false;
}
+ if (mAppCompatController.mAllowRestrictedResizability.getAsBoolean()) {
+ return false;
+ }
// If the user preference respects aspect ratio, then it becomes non-resizable.
return !mAppCompatController.getAppCompatOverrides().getAppCompatAspectRatioOverrides()
.shouldApplyUserMinAspectRatioOverride();
@@ -6069,7 +6076,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void notifyUnknownVisibilityLaunchedForKeyguardTransition() {
// No display activities never add a window, so there is no point in waiting them for
// relayout.
- if (noDisplay || !isKeyguardLocked()) {
+ if (mNoDisplay || !isKeyguardLocked()) {
return;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 05a96d9fcf5e..2e2ca147dcdd 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1037,7 +1037,6 @@ class ActivityStarter {
mLastStartReason = request.reason;
mLastStartActivityTimeMs = System.currentTimeMillis();
- final ActivityRecord previousStart = mLastStartActivityRecord;
final IApplicationThread caller = request.caller;
Intent intent = request.intent;
NeededUriGrants intentGrants = request.intentGrants;
@@ -2350,7 +2349,8 @@ class ActivityStarter {
// When there is a reused activity and the current result is a trampoline activity,
// set the reused activity as the result.
if (mLastStartActivityRecord != null
- && (mLastStartActivityRecord.finishing || mLastStartActivityRecord.noDisplay)) {
+ && (mLastStartActivityRecord.finishing
+ || mLastStartActivityRecord.isNoDisplay())) {
mLastStartActivityRecord = targetTaskTop;
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 30b53d1dbab4..cf178046d2e6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2478,7 +2478,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Notifies that the top activity of the task is forced to be resizeable. */
private void handleForcedResizableTaskIfNeeded(Task task, int reason) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity == null || topActivity.noDisplay
+ if (topActivity == null || topActivity.isNoDisplay()
|| !topActivity.canForceResizeNonResizable(task.getWindowingMode())) {
return;
}
@@ -2894,10 +2894,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
private boolean mIncludeInvisibleAndFinishing;
private boolean mIgnoringKeyguard;
- ActivityRecord getOpaqueActivity(
- @NonNull WindowContainer<?> container, boolean ignoringKeyguard) {
+ ActivityRecord getOpaqueActivity(@NonNull WindowContainer<?> container) {
mIncludeInvisibleAndFinishing = true;
- mIgnoringKeyguard = ignoringKeyguard;
+ mIgnoringKeyguard = true;
return container.getActivity(this,
true /* traverseTopToBottom */, null /* boundary */);
}
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
index 548c0a34bf99..fa2c71658022 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioPolicy.java
@@ -128,10 +128,11 @@ class AppCompatAspectRatioPolicy {
}
if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
&& !AppCompatCameraPolicy.shouldOverrideMinAspectRatioForCamera(mActivityRecord)) {
- if (mActivityRecord.isUniversalResizeable()) {
+ final float minAspectRatio = info.getMinAspectRatio();
+ if (minAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
return 0;
}
- return info.getMinAspectRatio();
+ return minAspectRatio;
}
if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
@@ -173,10 +174,11 @@ class AppCompatAspectRatioPolicy {
if (mTransparentPolicy.isRunning()) {
return mTransparentPolicy.getInheritedMaxAspectRatio();
}
- if (mActivityRecord.isUniversalResizeable()) {
+ final float maxAspectRatio = mActivityRecord.info.getMaxAspectRatio();
+ if (maxAspectRatio == 0 || mActivityRecord.isUniversalResizeable()) {
return 0;
}
- return mActivityRecord.info.getMaxAspectRatio();
+ return maxAspectRatio;
}
@Nullable
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 6c344c6d850a..145a3767c149 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -15,12 +15,15 @@
*/
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
@@ -47,6 +50,8 @@ class AppCompatController {
private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy;
@NonNull
private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy;
+ @NonNull
+ final BooleanSupplier mAllowRestrictedResizability;
AppCompatController(@NonNull WindowManagerService wmService,
@NonNull ActivityRecord activityRecord) {
@@ -70,6 +75,17 @@ class AppCompatController {
mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord,
mAppCompatOverrides);
+ mAllowRestrictedResizability = AppCompatUtils.asLazy(() -> {
+ try {
+ return packageManager.getPropertyAsUser(
+ PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ mActivityRecord.mActivityComponent.getPackageName(),
+ mActivityRecord.mActivityComponent.getClassName(),
+ mActivityRecord.mUserId).getBoolean();
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ });
}
@NonNull
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index db76eb9ac5d9..ebb50db54693 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -164,41 +164,46 @@ final class AppCompatUtils {
appCompatTaskInfo.setIsFromLetterboxDoubleTap(reachabilityOverrides.isFromDoubleTap());
- final Rect bounds = top.getBounds();
- final Rect appBounds = getAppBounds(top);
- appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
- appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
- appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
- appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
+ final boolean isTopActivityLetterboxed = top.areBoundsLetterboxed();
+ appCompatTaskInfo.setTopActivityLetterboxed(isTopActivityLetterboxed);
+ if (isTopActivityLetterboxed) {
+ final Rect bounds = top.getBounds();
+ final Rect appBounds = getAppBounds(top);
+ appCompatTaskInfo.topActivityLetterboxWidth = bounds.width();
+ appCompatTaskInfo.topActivityLetterboxHeight = bounds.height();
+ appCompatTaskInfo.topActivityLetterboxAppWidth = appBounds.width();
+ appCompatTaskInfo.topActivityLetterboxAppHeight = appBounds.height();
- // We need to consider if letterboxed or pillarboxed.
- // TODO(b/336807329) Encapsulate reachability logic
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
- .isLetterboxDoubleTapEducationEnabled());
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
- if (appCompatTaskInfo.isTopActivityPillarboxed()) {
- if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
- // Pillarboxed.
- appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
- reachabilityOverrides.getLetterboxPositionForHorizontalReachability();
- } else {
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
- }
- } else {
- if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
- // Letterboxed.
- appCompatTaskInfo.topActivityLetterboxVerticalPosition =
- reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+ // We need to consider if letterboxed or pillarboxed.
+ // TODO(b/336807329) Encapsulate reachability logic
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(reachabilityOverrides
+ .isLetterboxDoubleTapEducationEnabled());
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled()) {
+ if (appCompatTaskInfo.isTopActivityPillarboxShaped()) {
+ if (reachabilityOverrides.allowHorizontalReachabilityForThinLetterbox()) {
+ // Pillarboxed.
+ appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
+ reachabilityOverrides
+ .getLetterboxPositionForHorizontalReachability();
+ } else {
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ }
} else {
- appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ if (reachabilityOverrides.allowVerticalReachabilityForThinLetterbox()) {
+ // Letterboxed.
+ appCompatTaskInfo.topActivityLetterboxVerticalPosition =
+ reachabilityOverrides.getLetterboxPositionForVerticalReachability();
+ } else {
+ appCompatTaskInfo.setLetterboxDoubleTapEnabled(false);
+ }
}
}
}
+
final boolean eligibleForAspectRatioButton =
!info.isTopActivityTransparent && !appCompatTaskInfo.isTopActivityInSizeCompat()
&& aspectRatioOverrides.shouldEnableUserAspectRatioSettings();
appCompatTaskInfo.setEligibleForUserAspectRatioButton(eligibleForAspectRatioButton);
- appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode =
AppCompatCameraPolicy.getCameraCompatFreeformMode(top);
appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index e827f44cb2a2..e9e550e72a00 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -159,7 +159,6 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELD
import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields;
import static com.android.server.wm.utils.RegionUtils.forEachRectReverse;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -3426,14 +3425,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!mWmService.mSupportsHighPerfTransitions) {
return;
}
- if (!explicitRefreshRateHints()) {
- if (enable) {
- getPendingTransaction().setEarlyWakeupStart();
- } else {
- getPendingTransaction().setEarlyWakeupEnd();
- }
- return;
- }
if (enable) {
if (mTransitionPrefSession == null) {
mTransitionPrefSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -3446,10 +3437,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
void enableHighFrameRate(boolean enable) {
- if (!explicitRefreshRateHints()) {
- // Done by RefreshRatePolicy.
- return;
- }
if (enable) {
if (mHighFrameRateSession == null) {
mHighFrameRateSession = mWmService.mSystemPerformanceHinter.createSession(
@@ -7072,7 +7059,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
@Override
public void setImeInputTargetRequestedVisibility(boolean visible) {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
- // TODO(b/329229469) we won't have the statsToken in all cases, but should still log
+ // TODO(b/353463205) we won't have the statsToken in all cases, but should still log
try {
mRemoteInsetsController.setImeInputTargetRequestedVisibility(visible);
} catch (RemoteException e) {
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8627a473a1ff..76e8a70768c1 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1735,9 +1735,9 @@ public class DisplayPolicy {
}
// Show IME over the keyguard if the target allows it.
- final boolean showImeOverKeyguard = imeTarget != null && imeTarget.isVisible()
- && win.mIsImWindow && (imeTarget.canShowWhenLocked()
- || !imeTarget.canBeHiddenByKeyguard());
+ final boolean showImeOverKeyguard =
+ imeTarget != null && win.mIsImWindow && imeTarget.isDisplayed() && (
+ imeTarget.canShowWhenLocked() || !imeTarget.canBeHiddenByKeyguard());
if (showImeOverKeyguard) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index e9c6e93891df..5ed9612e4e83 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -100,13 +100,13 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// isLeashReadyForDispatching (used to dispatch the leash of the control) is
// depending on mGivenInsetsReady. Therefore, triggering notifyControlChanged here
// again, so that the control with leash can be eventually dispatched
- if (!mGivenInsetsReady && mServerVisible && !givenInsetsPending) {
+ if (!mGivenInsetsReady && isServerVisible() && !givenInsetsPending) {
mGivenInsetsReady = true;
ImeTracker.forLogging().onProgress(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStateController.notifyControlChanged(mControlTarget, this);
setImeShowing(true);
- } else if (wasServerVisible && mServerVisible && mGivenInsetsReady
+ } else if (wasServerVisible && isServerVisible() && mGivenInsetsReady
&& givenInsetsPending) {
// If the server visibility didn't change (still visible), and mGivenInsetsReady
// is set, we won't call into notifyControlChanged. Therefore, we can reset the
@@ -114,7 +114,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
ImeTracker.forLogging().onCancelled(mStatsToken,
ImeTracker.PHASE_WM_POST_LAYOUT_NOTIFY_CONTROLS_CHANGED);
mStatsToken = null;
- } else if (wasServerVisible && !mServerVisible) {
+ } else if (wasServerVisible && !isServerVisible()) {
setImeShowing(false);
}
}
@@ -134,11 +134,15 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
@Override
protected boolean isLeashReadyForDispatching() {
if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ // We should only dispatch the leash, if the following conditions are fulfilled:
+ // 1. parent isLeashReadyForDispatching, 2. mGivenInsetsReady (means there are no
+ // givenInsetsPending), 3. the IME surface is drawn, 4. either the IME is
+ // serverVisible (the unfrozen state)
final WindowState ws =
mWindowContainer != null ? mWindowContainer.asWindowState() : null;
final boolean isDrawn = ws != null && ws.isDrawn();
return super.isLeashReadyForDispatching()
- && mServerVisible && isDrawn && mGivenInsetsReady;
+ && isServerVisible() && isDrawn && mGivenInsetsReady;
} else {
return super.isLeashReadyForDispatching();
}
@@ -254,7 +258,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// Refer WindowState#getImeControlTarget().
target = target.getWindow().getImeControlTarget();
}
- // TODO(b/329229469) make sure that the statsToken of all callers is non-null (currently
+ // TODO(b/353463205) make sure that the statsToken of all callers is non-null (currently
// not the case)
super.updateControlForTarget(target, force, statsToken);
if (Flags.refactorInsetsController()) {
@@ -290,12 +294,14 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
changed |= mDisplayContent.onImeInsetsClientVisibilityUpdate();
if (Flags.refactorInsetsController()) {
if (changed) {
+ ImeTracker.forLogging().onProgress(statsToken,
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
invokeOnImeRequestedChangedListener(mDisplayContent.getImeInputTarget(),
statsToken);
} else {
- // TODO(b/329229469) change phase and check cancelled / failed
+ // TODO(b/353463205) check cancelled / failed
ImeTracker.forLogging().onCancelled(statsToken,
- ImeTracker.PHASE_CLIENT_REPORT_REQUESTED_VISIBLE_TYPES);
+ ImeTracker.PHASE_SERVER_UPDATE_CLIENT_VISIBILITY);
}
}
return changed;
@@ -460,7 +466,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider {
// This can later become ready, so we don't want to cancel the pending request here.
return;
}
- // TODO(b/329229469) check if this is still triggered, as we don't go into STATE_SHOW_IME
+ // TODO(b/353463205) check if this is still triggered, as we don't go into STATE_SHOW_IME
// (DefaultImeVisibilityApplier)
if (android.view.inputmethod.Flags.refactorInsetsController()) {
// The IME is drawn, so call into {@link WindowState#notifyInsetsControlChanged}
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 1d4d6eb82c44..7276007481ab 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -730,6 +730,10 @@ class InsetsSourceProvider {
return mFakeControlTarget;
}
+ boolean isServerVisible() {
+ return mServerVisible;
+ }
+
boolean isClientVisible() {
return mClientVisible;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 5dddf36a8d8b..4b2d45430bb4 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -317,9 +317,9 @@ class InsetsStateController {
// aborted.
provider.updateFakeControlTarget(target);
} else {
- // TODO(b/329229469) if the IME controlTarget changes, any pending requests should fail
+ // TODO(b/353463205) if the IME controlTarget changes, any pending requests should fail
provider.updateControlForTarget(target, false /* force */,
- null /* TODO(b/329229469) check if needed here */);
+ null /* TODO(b/353463205) check if needed here */);
// Get control target again in case the provider didn't accept the one we passed to it.
target = provider.getControlTarget();
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index 8cab7d979d07..e4c34ed52359 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -19,8 +19,6 @@ package com.android.server.wm;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_NONE;
import static android.hardware.display.DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
-
import android.hardware.display.DisplayManager;
import android.view.Display;
import android.view.Display.Mode;
@@ -60,7 +58,6 @@ class RefreshRatePolicy {
}
private final DisplayInfo mDisplayInfo;
- private final Mode mDefaultMode;
private final Mode mLowRefreshRateMode;
private final PackageRefreshRate mNonHighRefreshRatePackages = new PackageRefreshRate();
private final HighRefreshRateDenylist mHighRefreshRateDenylist;
@@ -92,8 +89,7 @@ class RefreshRatePolicy {
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateDenylist denylist) {
mDisplayInfo = displayInfo;
- mDefaultMode = displayInfo.getDefaultMode();
- mLowRefreshRateMode = findLowRefreshRateMode(displayInfo, mDefaultMode);
+ mLowRefreshRateMode = findLowRefreshRateMode(displayInfo);
mHighRefreshRateDenylist = denylist;
mWmService = wmService;
}
@@ -102,7 +98,8 @@ class RefreshRatePolicy {
* Finds the mode id with the lowest refresh rate which is >= 60hz and same resolution as the
* default mode.
*/
- private Mode findLowRefreshRateMode(DisplayInfo displayInfo, Mode defaultMode) {
+ private Mode findLowRefreshRateMode(DisplayInfo displayInfo) {
+ final Mode defaultMode = displayInfo.getDefaultMode();
float[] refreshRates = displayInfo.getDefaultRefreshRates();
float bestRefreshRate = defaultMode.getRefreshRate();
mMinSupportedRefreshRate = bestRefreshRate;
@@ -135,33 +132,6 @@ class RefreshRatePolicy {
// Unspecified, use default mode.
return 0;
}
-
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate. But if the display size of default mode is different
- // from the using preferred mode, then still keep the preferred mode to avoid disturbing
- // the animation.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
- Display.Mode preferredMode = null;
- for (Display.Mode mode : mDisplayInfo.supportedModes) {
- if (preferredDisplayModeId == mode.getModeId()) {
- preferredMode = mode;
- break;
- }
- }
- if (preferredMode != null) {
- final int pW = preferredMode.getPhysicalWidth();
- final int pH = preferredMode.getPhysicalHeight();
- if ((pW != mDefaultMode.getPhysicalWidth()
- || pH != mDefaultMode.getPhysicalHeight())
- && pW == mDisplayInfo.getNaturalWidth()
- && pH == mDisplayInfo.getNaturalHeight()) {
- // Prefer not to change display size when animating.
- return preferredDisplayModeId;
- }
- }
- return 0;
- }
-
return preferredDisplayModeId;
}
@@ -264,12 +234,6 @@ class RefreshRatePolicy {
return w.mFrameRateVote.reset();
}
- // If app is animating, it's not able to control refresh rate because we want the animation
- // to run in default refresh rate.
- if (!explicitRefreshRateHints() && w.isAnimationRunningSelfOrParent()) {
- return w.mFrameRateVote.reset();
- }
-
// If the app set a preferredDisplayModeId, the preferred refresh rate is the refresh rate
// of that mode id.
if (refreshRateSwitchingType != SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY) {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 077127c031e1..1bb4c41e79e0 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -715,7 +715,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (embeddedWindow != null) {
// If there is no WindowState for the IWindow, it could be still an
// EmbeddedWindow. Therefore, check the EmbeddedWindowController as well
- // TODO(b/329229469) Use different phase here
+ // TODO(b/353463205) Use different phase here
ImeTracker.forLogging().onProgress(imeStatsToken,
ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
embeddedWindow.setRequestedVisibleTypes(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7473accb8eee..352dc528f815 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3432,9 +3432,9 @@ class Task extends TaskFragment {
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
info.isVisibleRequested = isVisibleRequested();
+ info.isTopActivityNoDisplay = top != null && top.isNoDisplay();
info.isSleeping = shouldSleepActivities();
info.isTopActivityTransparent = top != null && !top.fillsParent();
- info.isTopActivityStyleFloating = top != null && top.isStyleFloating();
info.lastNonFullscreenBounds = topTask.mLastNonFullscreenBounds;
final WindowState windowState = top != null ? top.findMainWindow() : null;
info.requestedVisibleTypes = (windowState != null && Flags.enableFullyImmersiveInDesktop())
@@ -4724,7 +4724,7 @@ class Task extends TaskFragment {
}
}
if (likelyResolvedMode != WINDOWING_MODE_FULLSCREEN
- && topActivity != null && !topActivity.noDisplay
+ && topActivity != null && !topActivity.isNoDisplay()
&& topActivity.canForceResizeNonResizable(likelyResolvedMode)) {
// Inform the user that they are starting an app that may not work correctly in
// multi-window mode.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 606d51d8ec50..e090b1980c6d 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1090,8 +1090,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return true;
}
// Including finishing Activity if the TaskFragment is becoming invisible in the transition.
- return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this,
- true /* ignoringKeyguard */) == null;
+ return mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(this) == null;
}
/**
@@ -1734,6 +1733,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!hasDirectChildActivities()) {
return false;
}
+ if (mResumedActivity != null && mTransitionController.isTransientLaunch(mResumedActivity)) {
+ // Even if the transient activity is occluded, defer pausing (addToStopping will still
+ // be called) it until the transient transition is done. So the current resuming
+ // activity won't need to wait for additional pause complete.
+ return false;
+ }
ProtoLog.d(WM_DEBUG_STATES, "startPausing: taskFrag =%s " + "mResumedActivity=%s", this,
mResumedActivity);
diff --git a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
index 5c9a84db002a..c39671d76929 100644
--- a/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/TaskLaunchParamsModifier.java
@@ -449,7 +449,7 @@ class TaskLaunchParamsModifier implements LaunchParamsModifier {
// If the source activity is a no-display activity, pass on the launch display area token
// from source activity as currently preferred.
- if (taskDisplayArea == null && source != null && source.noDisplay) {
+ if (taskDisplayArea == null && source != null && source.isNoDisplay()) {
taskDisplayArea = source.mHandoverTaskDisplayArea;
if (taskDisplayArea != null) {
if (DEBUG) appendLog("display-area-from-no-display-source=" + taskDisplayArea);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 454e43120ede..a6034664af5a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -477,20 +477,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (transientRoot == null) continue;
final WindowContainer<?> rootParent = transientRoot.getParent();
if (rootParent == null || rootParent.getTopChild() == transientRoot) continue;
- final ActivityRecord topOpaque = mController.mAtm.mTaskSupervisor.mOpaqueActivityHelper
- .getOpaqueActivity(rootParent, true /* ignoringKeyguard */);
- if (transientRoot.compareTo(topOpaque.getRootTask()) < 0) {
- occludedCount++;
+ for (int j = rootParent.getChildCount() - 1; j >= 0; --j) {
+ final WindowContainer<?> sibling = rootParent.getChildAt(j);
+ if (sibling == transientRoot) break;
+ if (!sibling.getWindowConfiguration().isAlwaysOnTop() && mController.mAtm
+ .mTaskSupervisor.mOpaqueActivityHelper.getOpaqueActivity(sibling) != null) {
+ occludedCount++;
+ break;
+ }
}
}
if (occludedCount == numTransient) {
- for (int i = mTransientLaunches.size() - 1; i >= 0; --i) {
- if (mTransientLaunches.keyAt(i).isDescendantOf(task)) {
- // Keep transient activity visible until transition finished, so it won't pause
- // with transient-hide tasks that may delay resuming the next top.
- return true;
- }
- }
// Let transient-hide activities pause before transition is finished.
return false;
}
@@ -3589,6 +3586,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (wc.mWmService.mAtmService.mBackNavigationController.isMonitorTransitionTarget(wc)) {
flags |= TransitionInfo.FLAG_BACK_GESTURE_ANIMATED;
}
+ final TaskDisplayArea tda = wc.asTaskDisplayArea();
+ if (tda != null) {
+ flags |= TransitionInfo.FLAG_IS_TASK_DISPLAY_AREA;
+ }
final Task task = wc.asTask();
if (task != null) {
final ActivityRecord topActivity = task.getTopNonFinishingActivity();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index d01e29b2fd5e..079170a70433 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -157,6 +157,7 @@ import static com.android.server.wm.WindowStateProto.ANIMATING_EXIT;
import static com.android.server.wm.WindowStateProto.ANIMATOR;
import static com.android.server.wm.WindowStateProto.ATTRIBUTES;
import static com.android.server.wm.WindowStateProto.DESTROYING;
+import static com.android.server.wm.WindowStateProto.DIM_BOUNDS;
import static com.android.server.wm.WindowStateProto.DISPLAY_ID;
import static com.android.server.wm.WindowStateProto.FORCE_SEAMLESS_ROTATION;
import static com.android.server.wm.WindowStateProto.GIVEN_CONTENT_INSETS;
@@ -181,7 +182,6 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static com.android.window.flags.Flags.secureWindowState;
import static com.android.window.flags.Flags.surfaceTrustedOverlay;
@@ -4118,6 +4118,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mMergedLocalInsetsSources.valueAt(i).dumpDebug(proto, MERGED_LOCAL_INSETS_SOURCES);
}
}
+ if (getDimController() != null) {
+ final Rect dimBounds = getDimController().getDimBounds();
+ if (dimBounds != null) {
+ dimBounds.dumpDebug(proto, DIM_BOUNDS);
+ }
+ }
proto.end(token);
}
@@ -5297,12 +5303,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (voteChanged) {
getPendingTransaction()
.setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate,
- mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl,
- mFrameRateVote.mSelectionStrategy);
- }
-
+ mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS)
+ .setFrameRateSelectionStrategy(mSurfaceControl,
+ mFrameRateVote.mSelectionStrategy);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 5bde8b5a507c..44e237aa27de 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -614,6 +614,12 @@ class WindowToken extends WindowContainer<WindowState> {
final int rotation = getRelativeDisplayRotation();
if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
+ if (ActivityTaskManagerService.isPip2ExperimentEnabled() && asActivityRecord() != null
+ && mTransitionController.getWindowingModeAtStart(
+ asActivityRecord()) == WINDOWING_MODE_PINNED) {
+ // PiP handles fixed rotation animation in Shell, so do not create the rotation leash.
+ return null;
+ }
final SurfaceControl leash = makeSurface().setContainerLayer()
.setParent(getParentSurfaceControl())
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index ea0b02c58974..4dc3ca5ceea5 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -55,7 +55,6 @@ cc_library_static {
"com_android_server_powerstats_PowerStatsService.cpp",
"com_android_server_power_stats_CpuPowerStatsCollector.cpp",
"com_android_server_hint_HintManagerService.cpp",
- "com_android_server_SerialService.cpp",
"com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
"com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
"com_android_server_stats_pull_StatsPullAtomService.cpp",
diff --git a/services/core/jni/com_android_server_SerialService.cpp b/services/core/jni/com_android_server_SerialService.cpp
deleted file mode 100644
index 6600c981b68d..000000000000
--- a/services/core/jni/com_android_server_SerialService.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2011 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.
- */
-
-#define LOG_TAG "SerialServiceJNI"
-#include "utils/Log.h"
-
-#include "jni.h"
-#include <nativehelper/JNIPlatformHelp.h>
-#include "android_runtime/AndroidRuntime.h"
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-namespace android
-{
-
-static struct parcel_file_descriptor_offsets_t
-{
- jclass mClass;
- jmethodID mConstructor;
-} gParcelFileDescriptorOffsets;
-
-static jobject android_server_SerialService_open(JNIEnv *env, jobject /* thiz */, jstring path)
-{
- const char *pathStr = env->GetStringUTFChars(path, NULL);
-
- int fd = open(pathStr, O_RDWR | O_NOCTTY);
- if (fd < 0) {
- ALOGE("could not open %s", pathStr);
- env->ReleaseStringUTFChars(path, pathStr);
- return NULL;
- }
- env->ReleaseStringUTFChars(path, pathStr);
-
- jobject fileDescriptor = jniCreateFileDescriptor(env, fd);
- if (fileDescriptor == NULL) {
- close(fd);
- return NULL;
- }
- return env->NewObject(gParcelFileDescriptorOffsets.mClass,
- gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
-}
-
-
-static const JNINativeMethod method_table[] = {
- { "native_open", "(Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;",
- (void*)android_server_SerialService_open },
-};
-
-int register_android_server_SerialService(JNIEnv *env)
-{
- jclass clazz = env->FindClass("com/android/server/SerialService");
- if (clazz == NULL) {
- ALOGE("Can't find com/android/server/SerialService");
- return -1;
- }
-
- clazz = env->FindClass("android/os/ParcelFileDescriptor");
- LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
- gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
- gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
- LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
- "Unable to find constructor for android.os.ParcelFileDescriptor");
-
- return jniRegisterNativeMethods(env, "com/android/server/SerialService",
- method_table, NELEM(method_table));
-}
-
-};
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 3c55d18245d7..59d7365d957d 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -33,7 +33,6 @@ int register_android_server_PowerStatsService(JNIEnv* env);
int register_android_server_power_stats_CpuPowerStatsCollector(JNIEnv* env);
int register_android_server_HintManagerService(JNIEnv* env);
int register_android_server_storage_AppFuse(JNIEnv* env);
-int register_android_server_SerialService(JNIEnv* env);
int register_android_server_SystemServer(JNIEnv* env);
int register_android_server_UsbAlsaJackDetector(JNIEnv* env);
int register_android_server_UsbAlsaMidiDevice(JNIEnv* env);
@@ -94,7 +93,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_PowerStatsService(env);
register_android_server_power_stats_CpuPowerStatsCollector(env);
register_android_server_HintManagerService(env);
- register_android_server_SerialService(env);
register_android_server_InputManager(env);
register_android_server_LightsService(env);
register_android_server_UsbDeviceManager(vm, env);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index a58da81c6396..984105865f7d 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -90,6 +90,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
/**
* Class responsible for setting, resolving, and enforcing policies set by multiple management
@@ -1030,11 +1031,11 @@ final class DevicePolicyEngine {
}
}
- private <V> void enforcePolicy(PolicyDefinition<V> policyDefinition,
+ private <V> CompletableFuture<Boolean> enforcePolicy(PolicyDefinition<V> policyDefinition,
@Nullable PolicyValue<V> policyValue, int userId) {
// null policyValue means remove any enforced policies, ensure callbacks handle this
// properly
- policyDefinition.enforcePolicy(
+ return policyDefinition.enforcePolicy(
policyValue == null ? null : policyValue.getValue(), mContext, userId);
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index aca6f7235714..4ce18d232936 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -255,7 +255,6 @@ import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPR
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_BLOCKING;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
-import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
import static android.provider.Settings.Global.PRIVATE_DNS_SPECIFIER;
import static android.provider.Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED;
import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
@@ -462,7 +461,6 @@ import android.permission.PermissionControllerManager;
import android.provider.CalendarContract;
import android.provider.ContactsContract.QuickContact;
import android.provider.ContactsInternal;
-import android.provider.DeviceConfig;
import android.provider.Settings;
import android.provider.Settings.Global;
import android.provider.Telephony;
@@ -908,10 +906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ "management app's authentication policy";
private static final String NOT_SYSTEM_CALLER_MSG = "Only the system can %s";
- private static final String PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG =
- "enable_permission_based_access";
- private static final boolean DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG = false;
-
private static final int RETRY_COPY_ACCOUNT_ATTEMPTS = 3;
/**
@@ -3557,6 +3551,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return true;
}
+
+
+ @GuardedBy("getLockObject()")
+ private boolean maybeMigrateMemoryTaggingLocked(String backupId) {
+ if (!Flags.setMtePolicyCoexistence()) {
+ Slog.i(LOG_TAG, "Memory Tagging not migrated because coexistence "
+ + "support is disabled.");
+ return false;
+ }
+ if (mOwners.isMemoryTaggingMigrated()) {
+ // TODO: Remove log after Flags.setMtePolicyCoexistence full rollout.
+ Slog.v(LOG_TAG, "Memory Tagging was previously migrated to policy engine.");
+ return false;
+ }
+
+ Slog.i(LOG_TAG, "Migrating Memory Tagging to policy engine");
+
+ // Create backup if none exists
+ mDevicePolicyEngine.createBackup(backupId);
+ try {
+ iterateThroughDpcAdminsLocked((admin, enforcingAdmin) -> {
+ if (admin.mtePolicy != 0) {
+ Slog.i(LOG_TAG, "Setting Memory Tagging policy");
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ enforcingAdmin,
+ new IntegerPolicyValue(admin.mtePolicy),
+ true /* No need to re-set system properties */);
+ }
+ });
+ } catch (Exception e) {
+ Slog.wtf(LOG_TAG,
+ "Failed to migrate Memory Tagging to policy engine", e);
+ }
+
+ Slog.i(LOG_TAG, "Marking Memory Tagging migration complete");
+ mOwners.markMemoryTaggingMigrated();
+ return true;
+ }
+
/** Register callbacks for statsd pulled atoms. */
private void registerStatsCallbacks() {
final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
@@ -4646,22 +4680,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@GuardedBy("getLockObject()")
private List<ActiveAdmin> getActiveAdminsForLockscreenPoliciesLocked(int userHandle) {
if (isSeparateProfileChallengeEnabled(userHandle)) {
-
- if (isPermissionCheckFlagEnabled()) {
- return getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(userHandle);
- }
// If this user has a separate challenge, only return its restrictions.
return getUserDataUnchecked(userHandle).mAdminList;
}
// If isSeparateProfileChallengeEnabled is false and userHandle points to a managed profile
// we need to query the parent user who owns the credential.
- if (isPermissionCheckFlagEnabled()) {
- return getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(getProfileParentId(userHandle),
- (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
- } else {
- return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
- (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
- }
+ return getActiveAdminsForUserAndItsManagedProfilesLocked(getProfileParentId(userHandle),
+ (user) -> !mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id));
}
@@ -4684,33 +4709,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
(user) -> mLockPatternUtils.isProfileWithUnifiedChallenge(user.id));
}
- /**
- * Get the list of active admins for an affected user:
- * <ul>
- * <li>The active admins associated with the userHandle itself</li>
- * <li>The parent active admins for each managed profile associated with the userHandle</li>
- * <li>The permission based admin associated with the userHandle itself</li>
- * </ul>
- *
- * @param userHandle the affected user for whom to get the active admins
- * @return the list of active admins for the affected user
- */
- @GuardedBy("getLockObject()")
- private List<ActiveAdmin> getActiveAdminsForAffectedUserInclPermissionBasedAdminLocked(
- int userHandle) {
- List<ActiveAdmin> list;
-
- if (isManagedProfile(userHandle)) {
- list = getUserDataUnchecked(userHandle).mAdminList;
- }
- list = getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(userHandle,
- /* shouldIncludeProfileAdmins */ (user) -> false);
-
- if (getUserData(userHandle).mPermissionBasedAdmin != null) {
- list.add(getUserData(userHandle).mPermissionBasedAdmin);
- }
- return list;
- }
/**
* Returns the list of admins on the given user, as well as parent admins for each managed
@@ -4763,44 +4761,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mDevicePolicyEngine.getResolvedPolicyAcrossUsers(policyDefinition, users);
}
- /**
- * Returns the list of admins on the given user, as well as parent admins for each managed
- * profile associated with the given user. Optionally also include the admin of each managed
- * profile.
- * <p> Should not be called on a profile user.
- */
- @GuardedBy("getLockObject()")
- private List<ActiveAdmin> getActiveAdminsForUserAndItsManagedProfilesInclPermissionBasedAdminLocked(int userHandle,
- Predicate<UserInfo> shouldIncludeProfileAdmins) {
- ArrayList<ActiveAdmin> admins = new ArrayList<>();
- mInjector.binderWithCleanCallingIdentity(() -> {
- for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
- DevicePolicyData policy = getUserDataUnchecked(userInfo.id);
- if (userInfo.id == userHandle) {
- admins.addAll(policy.mAdminList);
- if (policy.mPermissionBasedAdmin != null) {
- admins.add(policy.mPermissionBasedAdmin);
- }
- } else if (userInfo.isManagedProfile()) {
- for (int i = 0; i < policy.mAdminList.size(); i++) {
- ActiveAdmin admin = policy.mAdminList.get(i);
- if (admin.hasParentActiveAdmin()) {
- admins.add(admin.getParentActiveAdmin());
- }
- if (shouldIncludeProfileAdmins.test(userInfo)) {
- admins.add(admin);
- }
- }
- if (policy.mPermissionBasedAdmin != null
- && shouldIncludeProfileAdmins.test(userInfo)) {
- admins.add(policy.mPermissionBasedAdmin);
- }
- }
- }
- });
- return admins;
- }
-
private boolean isSeparateProfileChallengeEnabled(int userHandle) {
return mInjector.binderWithCleanCallingIdentity(() ->
mLockPatternUtils.isSeparateProfileChallengeEnabled(userHandle));
@@ -4893,25 +4853,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
+ Objects.requireNonNull(who, "ComponentName is null");
Preconditions.checkArgumentNonnegative(timeout, "Timeout must be >= 0 ms");
int userHandle = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUserId)
- .getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
- }
+ ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_EXPIRE_PASSWORD, parent);
// Calling this API automatically bumps the expiration date
final long expiration = timeout > 0L ? (timeout + System.currentTimeMillis()) : 0L;
ap.passwordExpirationDate = expiration;
@@ -4972,28 +4922,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean addCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
String packageName) {
- CallerIdentity caller;
+ CallerIdentity caller = getCallerIdentity(admin);
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
- ActiveAdmin activeAdmin;
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
List<String> changedProviders = null;
@@ -5026,28 +4962,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean removeCrossProfileWidgetProvider(ComponentName admin, String callerPackageName,
String packageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
+ CallerIdentity caller = getCallerIdentity(admin);
- ActiveAdmin activeAdmin;
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
List<String> changedProviders = null;
@@ -5080,27 +5002,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public List<String> getCrossProfileWidgetProviders(ComponentName admin,
String callerPackageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
- ActiveAdmin activeAdmin;
+ CallerIdentity caller = getCallerIdentity(admin);
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- admin,
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- caller.getUserId());
- activeAdmin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isProfileOwner(caller));
- synchronized (getLockObject()) {
- activeAdmin = getProfileOwnerLocked(caller.getUserId());
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isProfileOwner(caller));
+
+ ActiveAdmin activeAdmin;
+ synchronized (getLockObject()) {
+ activeAdmin = getProfileOwnerLocked(caller.getUserId());
}
synchronized (getLockObject()) {
@@ -5449,24 +5358,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
enforceUserUnlocked(userHandle, parent);
synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
- enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- callerPackageName, affectedUser);
- } else {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(
- null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
- }
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(
+ null, DeviceAdminInfo.USES_POLICY_LIMIT_PASSWORD, parent);
int credentialOwner = getCredentialOwner(userHandle, parent);
DevicePolicyData policy = getUserDataUnchecked(credentialOwner);
PasswordMetrics metrics = mLockSettingsInternal.getUserPasswordMetrics(credentialOwner);
final int userToCheck = getProfileParentUserIfRequested(userHandle, parent);
- boolean activePasswordSufficientForUserLocked = isActivePasswordSufficientForUserLocked(
+ return isActivePasswordSufficientForUserLocked(
policy.mPasswordValidAtLastCheckpoint, metrics, userToCheck);
- return activePasswordSufficientForUserLocked;
}
}
@@ -5622,21 +5524,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
isDefaultDeviceOwner(caller) || isProfileOwner(caller) || isSystemUid(caller),
"Only profile owner, device owner and system may call this method on parent.");
} else {
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || hasCallingOrSelfPermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS)
- || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
- "Must have " + REQUEST_PASSWORD_COMPLEXITY + " or " +
- MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS
- + " permissions, or be a profile owner or device owner.");
- } else {
- Preconditions.checkCallAuthorization(
- hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
- || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
- "Must have " + REQUEST_PASSWORD_COMPLEXITY
- + " permission, or be a profile owner or device owner.");
- }
+ Preconditions.checkCallAuthorization(
+ hasCallingOrSelfPermission(REQUEST_PASSWORD_COMPLEXITY)
+ || isDefaultDeviceOwner(caller) || isProfileOwner(caller),
+ "Must have " + REQUEST_PASSWORD_COMPLEXITY
+ + " permission, or be a profile owner or device owner.");
}
synchronized (getLockObject()) {
@@ -5728,26 +5620,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private void setRequiredPasswordComplexityPreCoexistence(
String callerPackageName, int passwordComplexity, boolean calledOnParent) {
CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
- }
+
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+ Preconditions.checkArgument(!calledOnParent || isProfileOwner(caller));
synchronized (getLockObject()) {
ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- // TODO: Make sure this returns the parent of the fake admin
- // TODO: Deal with null componentname
- int affectedUser = calledOnParent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- admin = enforcePermissionAndGetEnforcingAdmin(
- null, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUser).getActiveAdmin();
- } else {
- admin = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
- }
+ admin = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), calledOnParent);
if (admin.mPasswordComplexity != passwordComplexity) {
// We require the caller to explicitly clear any password quality requirements set
@@ -5907,14 +5788,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!isSystemUid(caller)) {
// This API can be called by an active device admin or by keyguard code.
if (!hasCallingPermission(permission.ACCESS_KEYGUARD_SECURE_STORAGE)) {
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent ? getProfileParentId(userHandle) : userHandle;
- enforcePermission(MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- callerPackageName, affectedUser);
- } else {
- getActiveAdminForCallerLocked(
- null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
- }
+ getActiveAdminForCallerLocked(
+ null, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
}
}
@@ -5931,31 +5806,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
-
+ Objects.requireNonNull(who, "ComponentName is null");
int userId = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userId) : userId;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who,
- /*permission=*/ MANAGE_DEVICE_POLICY_WIPE_DATA,
- /* adminPolicy=*/ DeviceAdminInfo.USES_POLICY_WIPE_DATA,
- caller.getPackageName(), affectedUserId).getActiveAdmin();
- } else {
- // This API can only be called by an active device admin,
- // so try to retrieve it to check that the caller is one.
- getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
- }
+ // This API can only be called by an active device admin,
+ // so try to retrieve it to check that the caller is one.
+ getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_WIPE_DATA, parent);
+ ActiveAdmin ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, parent);
if (ap.maximumFailedPasswordsForWipe != num) {
ap.maximumFailedPasswordsForWipe = num;
@@ -6210,25 +6072,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(who, "ComponentName is null");
- }
+
+ Objects.requireNonNull(who, "ComponentName is null");
+
int userHandle = mInjector.userHandleGetCallingUserId();
int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- ap = enforcePermissionAndGetEnforcingAdmin(
- who,
- /*permission=*/ MANAGE_DEVICE_POLICY_LOCK,
- /*AdminPolicy=*/DeviceAdminInfo.USES_POLICY_FORCE_LOCK,
- caller.getPackageName(),
- affectedUserId).getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(
- who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
- }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_FORCE_LOCK, parent);
if (ap.maximumTimeToUnlock != timeMs) {
ap.maximumTimeToUnlock = timeMs;
@@ -6334,16 +6185,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature || !mLockPatternUtils.hasSecureLockScreen()) {
return;
}
+
Preconditions.checkArgument(timeoutMs >= 0, "Timeout must not be a negative number.");
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwner(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwner(caller));
+
// timeoutMs with value 0 means that the admin doesn't participate
// timeoutMs is clamped to the interval in case the internal constants change in the future
final long minimumStrongAuthTimeout = getMinimumStrongAuthTimeoutMs();
@@ -6357,17 +6205,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int userHandle = caller.getUserId();
boolean changed = false;
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- int affectedUser = parent
- ? getProfileParentId(caller.getUserId()) : caller.getUserId();
- ap = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
- caller.getPackageName(), affectedUser).getActiveAdmin();
- } else {
- ap = getParentOfAdminIfRequired(
- getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
- }
+ ActiveAdmin ap = getParentOfAdminIfRequired(
+ getProfileOwnerOrDeviceOwnerLocked(caller.getUserId()), parent);
if (ap.strongAuthUnlockTimeout != timeoutMs) {
ap.strongAuthUnlockTimeout = timeoutMs;
saveSettingsLocked(userHandle);
@@ -6664,16 +6503,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(!isUserSelectable, "The credential "
+ "management app is not allowed to install a user selectable key pair");
@@ -6733,16 +6565,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -6802,13 +6627,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean canInstallCertificates(CallerIdentity caller) {
- if (isPermissionCheckFlagEnabled()) {
- return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId());
- } else {
- return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
- || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
- }
+ return isProfileOwner(caller) || isDefaultDeviceOwner(caller)
+ || isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
}
private boolean canChooseCertificates(CallerIdentity caller) {
@@ -7001,16 +6821,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
caller.getPackageName(), caller.getUid()));
enforceIndividualAttestationSupportedIfRequested(attestationUtilsFlags);
} else {
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
- caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
- isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(
+ caller) || isDefaultDeviceOwner(caller))) || (caller.hasPackage() && (
+ isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -7143,16 +6956,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final CallerIdentity caller = getCallerIdentity(who, callerPackage);
final boolean isCallerDelegate = isCallerDelegate(caller, DELEGATION_CERT_INSTALL);
final boolean isCredentialManagementApp = isCredentialManagementApp(caller);
- if (isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES,
- caller.getPackageName(), caller.getUserId())
- || isCredentialManagementApp);
- } else {
- Preconditions.checkCallAuthorization((caller.hasAdminComponent()
- && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
- || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
- }
+ Preconditions.checkCallAuthorization((caller.hasAdminComponent()
+ && (isProfileOwner(caller) || isDefaultDeviceOwner(caller)))
+ || (caller.hasPackage() && (isCallerDelegate || isCredentialManagementApp)));
if (isCredentialManagementApp) {
Preconditions.checkCallAuthorization(
isAliasInCredentialManagementAppPolicy(caller, alias),
@@ -8285,29 +8091,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkNotNull(who, "ComponentName is null");
- }
+
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
CallerIdentity caller = getCallerIdentity(who, callerPackageName);
- if (!isPermissionCheckFlagEnabled()) {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
+
checkCanExecuteOrThrowUnsafe(DevicePolicyManager
.OPERATION_SET_FACTORY_RESET_PROTECTION_POLICY);
final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
synchronized (getLockObject()) {
ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(
- who, MANAGE_DEVICE_POLICY_FACTORY_RESET, caller.getPackageName(),
- UserHandle.USER_ALL)
- .getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
admin.mFactoryResetProtectionPolicy = policy;
saveSettingsLocked(caller.getUserId());
}
@@ -8347,7 +8145,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|| hasCallingPermission(permission.MASTER_CLEAR)
|| hasCallingPermission(MANAGE_DEVICE_POLICY_FACTORY_RESET),
"Must be called by the FRP management agent on device");
- admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
@@ -10247,15 +10045,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return admin;
}
- ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked() {
- ensureLocked();
- ActiveAdmin doOrPo = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- if (isPermissionCheckFlagEnabled() && doOrPo == null) {
- return getUserData(0).mPermissionBasedAdmin;
- }
- return doOrPo;
- }
-
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -10998,8 +10787,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
* (2.1.1) The caller is the profile owner.
* (2.1.2) The caller is from another app in the same user as the profile owner, AND
* the caller is the delegated cert installer.
- * (3) The caller holds the
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_CERTIFICATES} permission.
*
* For the device owner case, simply check that the caller is the device owner or the
* delegated certificate installer.
@@ -11013,24 +10800,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@VisibleForTesting
boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
final int userId = UserHandle.getUserId(uid);
- // TODO(b/280048070): Introduce a permission to handle device ID access
- if (isPermissionCheckFlagEnabled()
- && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
- return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
- } else {
- ComponentName deviceOwner = getDeviceOwnerComponent(true);
- if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
- || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
- return true;
- }
- ComponentName profileOwner = getProfileOwnerAsUser(userId);
- final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
- && (profileOwner.getPackageName().equals(packageName)
- || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
- if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
- || isUserAffiliatedWithDevice(userId))) {
- return true;
- }
+ ComponentName deviceOwner = getDeviceOwnerComponent(true);
+ if (deviceOwner != null && (deviceOwner.getPackageName().equals(packageName)
+ || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL))) {
+ return true;
+ }
+ ComponentName profileOwner = getProfileOwnerAsUser(userId);
+ final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
+ && (profileOwner.getPackageName().equals(packageName)
+ || isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
+ if (isCallerProfileOwnerOrDelegate && (isProfileOwnerOfOrganizationOwnedDevice(userId)
+ || isUserAffiliatedWithDevice(userId))) {
+ return true;
}
return false;
}
@@ -11731,25 +11512,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setDefaultSmsApplication(ComponentName admin, String callerPackageName,
String packageName, boolean parent) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- } else {
- caller = getCallerIdentity(admin);
- }
+ CallerIdentity caller = getCallerIdentity(admin);
- final int userId;
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_DEFAULT_SMS,
- caller.getPackageName(),
- getAffectedUser(parent));
- } else {
- Objects.requireNonNull(admin, "ComponentName is null");
- Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
if (!parent && isManagedProfile(caller.getUserId())
&& getManagedSubscriptionsPolicy().getPolicyType()
@@ -11759,6 +11527,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
+ "ManagedSubscriptions policy is set");
}
+ final int userId;
if (parent) {
userId = getProfileParentId(mInjector.userHandleGetCallingUserId());
mInjector.binderWithCleanCallingIdentity(() -> enforcePackageIsSystemPackage(
@@ -11957,10 +11726,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "admin is null");
- }
-
+ Objects.requireNonNull(admin, "admin is null");
Objects.requireNonNull(agent, "agent is null");
PolicySizeVerifier.enforceMaxPackageNameLength(agent.getPackageName());
@@ -11972,19 +11738,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
int userHandle = mInjector.userHandleGetCallingUserId();
synchronized (getLockObject()) {
- ActiveAdmin ap;
- if (isPermissionCheckFlagEnabled()) {
- CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
- int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
- ap = enforcePermissionAndGetEnforcingAdmin(
- admin,
- /*permission=*/MANAGE_DEVICE_POLICY_KEYGUARD,
- /*adminPolicy=*/DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES,
- caller.getPackageName(), affectedUserId).getActiveAdmin();
- } else {
- ap = getActiveAdminForCallerLocked(admin,
- DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
- }
+ ActiveAdmin ap = getActiveAdminForCallerLocked(admin,
+ DeviceAdminInfo.USES_POLICY_DISABLE_KEYGUARD_FEATURES, parent);
checkCanExecuteOrThrowUnsafe(
DevicePolicyManager.OPERATION_SET_TRUST_AGENT_CONFIGURATION);
@@ -12080,27 +11835,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void addCrossProfileIntentFilter(ComponentName who, String callerPackageName,
IntentFilter filter, int flags) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- int callingUserId = caller.getUserId();
+ CallerIdentity caller = getCallerIdentity(who);
+
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- callingUserId);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- }
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
+ int callingUserId = caller.getUserId();
UserInfo parent = mUserManager.getProfileParent(callingUserId);
if (parent == null) {
Slogf.e(LOG_TAG, "Cannot call addCrossProfileIntentFilter if there is no "
@@ -12144,28 +11888,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void clearCrossProfileIntentFilters(ComponentName who, String callerPackageName) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- int callingUserId = caller.getUserId();
+ CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(
- MANAGE_DEVICE_POLICY_PROFILE_INTERACTION,
- caller.getPackageName(),
- callingUserId);
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isProfileOwner(caller) || isDefaultDeviceOwner(caller));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isProfileOwner(caller) || isDefaultDeviceOwner(caller));
synchronized (getLockObject()) {
long id = mInjector.binderClearCallingIdentity();
try {
+ int callingUserId = caller.getUserId();
UserInfo parent = mUserManager.getProfileParent(callingUserId);
if (parent == null) {
Slogf.e(LOG_TAG, "Cannot call clearCrossProfileIntentFilter if there is no "
@@ -15166,19 +14898,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Preconditions.checkNotNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
@@ -15197,16 +14922,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, who.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- Preconditions.checkNotNull(who, "ComponentName is null");
-
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalGetInt(Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) > 0);
@@ -15294,18 +15013,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean setTime(@Nullable ComponentName who, String callerPackageName, long millis) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- // This is a global action.
- enforcePermission(SET_TIME, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set time when auto time is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) == 1) {
@@ -15322,18 +15034,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public boolean setTimeZone(@Nullable ComponentName who, String callerPackageName,
String timeZone) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- // This is a global action.
- enforcePermission(SET_TIME_ZONE, caller.getPackageName(), UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
// Don't allow set timezone when auto timezone is on.
if (mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) == 1) {
@@ -16537,22 +16242,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
policy.validateAgainstPreviousFreezePeriod(record.first, record.second,
LocalDate.now());
}
- CallerIdentity caller;
- synchronized (getLockObject()) {
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(who);
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
+ CallerIdentity caller = getCallerIdentity(who);
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller)
|| isDefaultDeviceOwner(caller));
- }
- checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
+ checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_SYSTEM_UPDATE_POLICY);
+ synchronized (getLockObject()) {
if (policy == null) {
mOwners.clearSystemUpdatePolicy();
} else {
@@ -16699,7 +16397,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mUserManager.getUserInfo(UserHandle.getCallingUserId()).isMain()) {
Slogf.w(LOG_TAG, "Only the system update service in the main user can broadcast "
+ "update information.");
- return;
}
});
@@ -16723,7 +16420,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
// Get running users.
- final int runningUserIds[];
+ final int[] runningUserIds;
try {
runningUserIds = mInjector.getIActivityManager().getRunningUserIds();
} catch (RemoteException e) {
@@ -16966,10 +16663,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
}
- if (!isRuntimePermission(permission)) {
- return false;
- }
- return true;
+ return isRuntimePermission(permission);
}
private void enforcePermissionGrantStateOnFinancedDevice(
@@ -17384,18 +17078,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public String getWifiMacAddress(ComponentName admin, String callerPackageName) {
-// if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "ComponentName is null");
-// }
+ Objects.requireNonNull(admin, "ComponentName is null");
final CallerIdentity caller = getCallerIdentity(admin, callerPackageName);
-// if (isPermissionCheckFlagEnabled()) {
-// enforcePermission(MANAGE_DEVICE_POLICY_WIFI, UserHandle.USER_ALL);
-// } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
-// }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
return mInjector.binderWithCleanCallingIdentity(() -> {
String[] macAddresses = mInjector.getWifiManager().getFactoryMacAddresses();
@@ -17462,25 +17150,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return;
}
- CallerIdentity caller;
- ActiveAdmin admin;
message = PolicySizeVerifier.truncateIfLonger(message, MAX_SHORT_SUPPORT_MESSAGE_LENGTH);
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- synchronized (getLockObject()) {
- admin = getActiveAdminForUidLocked(who, caller.getUid());
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getActiveAdminForUidLocked(who, caller.getUid());
}
synchronized (getLockObject()) {
@@ -17501,23 +17179,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (!mHasFeature) {
return null;
}
- CallerIdentity caller;
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- caller = getCallerIdentity(who);
- Objects.requireNonNull(who, "ComponentName is null");
- synchronized (getLockObject()) {
- admin = getActiveAdminForUidLocked(who, caller.getUid());
- }
+ CallerIdentity caller = getCallerIdentity(who);
+ Objects.requireNonNull(who, "ComponentName is null");
+
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getActiveAdminForUidLocked(who, caller.getUid());
}
return admin.shortSupportMessage;
}
@@ -17680,26 +17348,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return;
}
CallerIdentity caller = getCallerIdentity(who);
- ActiveAdmin admin = null;
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
text = PolicySizeVerifier.truncateIfLonger(text, MAX_ORG_NAME_LENGTH);
synchronized (getLockObject()) {
- if (!isPermissionCheckFlagEnabled()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (!TextUtils.equals(admin.organizationName, text)) {
admin.organizationName = (text == null || text.length() == 0)
? null : text.toString();
@@ -17714,23 +17370,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return null;
}
CallerIdentity caller = getCallerIdentity(who);
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforceCanQueryAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
- Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallingUser(isManagedProfile(caller.getUserId()));
+ Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller));
- synchronized (getLockObject()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
}
return admin.organizationName;
@@ -18214,28 +17861,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
final CallerIdentity caller = getCallerIdentity(admin, packageName);
- if (isPermissionCheckFlagEnabled()) {
- synchronized (getLockObject()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- enforcePermission(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, caller.getPackageName(),
- UserHandle.USER_ALL);
- }
+ if (admin != null) {
+ Preconditions.checkCallAuthorization(
+ isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isDefaultDeviceOwner(caller));
} else {
- if (admin != null) {
- Preconditions.checkCallAuthorization(
- isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isDefaultDeviceOwner(caller));
- } else {
- // A delegate app passes a null admin component, which is expected
- Preconditions.checkCallAuthorization(
- isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
- }
+ // A delegate app passes a null admin component, which is expected
+ Preconditions.checkCallAuthorization(
+ isCallerDelegate(caller, DELEGATION_SECURITY_LOGGING));
+ }
- synchronized (getLockObject()) {
- Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
- || areAllUsersAffiliatedWithDeviceLocked());
- }
+ synchronized (getLockObject()) {
+ Preconditions.checkCallAuthorization(isOrganizationOwnedDeviceWithManagedProfile()
+ || areAllUsersAffiliatedWithDeviceLocked());
}
DevicePolicyEventLogger
@@ -18259,7 +17897,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return new ParceledListSlice<SecurityEvent>(output);
} catch (IOException e) {
Slogf.w(LOG_TAG, "Fail to read previous events" , e);
- return new ParceledListSlice<SecurityEvent>(Collections.<SecurityEvent>emptyList());
+ return new ParceledListSlice<SecurityEvent>(Collections.emptyList());
}
}
@@ -18752,8 +18390,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private boolean hasIncompatibleAccounts(int userId) {
- return mHasIncompatibleAccounts == null ? true
- : mHasIncompatibleAccounts.getOrDefault(userId, /* default= */ false);
+ return mHasIncompatibleAccounts == null || mHasIncompatibleAccounts.getOrDefault(
+ userId, /* default= */ false);
}
/**
@@ -18870,7 +18508,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return false;
}
}
- };
+ }
private boolean isAdb(CallerIdentity caller) {
return isShellUid(caller) || isRootUid(caller);
@@ -20168,21 +19806,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void installUpdateFromFile(ComponentName admin, String callerPackageName,
ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback callback) {
- if (!isPermissionCheckFlagEnabled()) {
- Objects.requireNonNull(admin, "ComponentName is null");
- }
+ Objects.requireNonNull(admin, "ComponentName is null");
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(admin, callerPackageName);
- enforcePermission(MANAGE_DEVICE_POLICY_SYSTEM_UPDATES, caller.getPackageName(),
- UserHandle.USER_ALL);
- } else {
- caller = getCallerIdentity(admin);
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
- }
+ CallerIdentity caller = getCallerIdentity(admin);
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_INSTALL_SYSTEM_UPDATE);
DevicePolicyEventLogger
@@ -20752,32 +20381,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setCommonCriteriaModeEnabled(ComponentName who, String callerPackageName,
boolean enabled) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(who, callerPackageName);
- } else {
- caller = getCallerIdentity(who);
- }
- final ActiveAdmin admin;
+ CallerIdentity caller = getCallerIdentity(who);
- if (isPermissionCheckFlagEnabled()) {
- EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
- who,
- MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE,
- caller.getPackageName(),
- caller.getUserId());
- admin = enforcingAdmin.getActiveAdmin();
- } else {
- Objects.requireNonNull(who, "ComponentName is null");
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "Common Criteria mode can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- synchronized (getLockObject()) {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
- }
+ Objects.requireNonNull(who, "ComponentName is null");
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Common Criteria mode can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
synchronized (getLockObject()) {
+ final ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
admin.mCommonCriteriaMode = enabled;
saveSettingsLocked(caller.getUserId());
}
@@ -20809,7 +20421,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
// their ActiveAdmin, instead of iterating through all admins.
ActiveAdmin admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null ? admin.mCommonCriteriaMode : false;
+ return admin != null && admin.mCommonCriteriaMode;
}
}
@@ -22209,7 +21821,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else {
owner = getDeviceOrProfileOwnerAdminLocked(userId);
}
- boolean canGrant = owner != null ? owner.mAdminCanGrantSensorsPermissions : false;
+ boolean canGrant = owner != null && owner.mAdminCanGrantSensorsPermissions;
mPolicyCache.setAdminCanGrantSensorsPermissions(canGrant);
}
}
@@ -22408,27 +22020,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setMinimumRequiredWifiSecurityLevel(String callerPackageName, int level) {
- CallerIdentity caller;
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(callerPackageName);
- } else {
- caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "Wi-Fi minimum security level can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- }
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "Wi-Fi minimum security level can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
boolean valueChanged = false;
synchronized (getLockObject()) {
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(/* admin= */ null,
- MANAGE_DEVICE_POLICY_WIFI, caller.getPackageName(), caller.getUserId())
- .getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (admin.mWifiMinimumSecurityLevel != level) {
admin.mWifiMinimumSecurityLevel = level;
saveSettingsLocked(caller.getUserId());
@@ -22450,21 +22050,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public WifiSsidPolicy getWifiSsidPolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity();
- if (isPermissionCheckFlagEnabled()) {
- enforcePermission(MANAGE_DEVICE_POLICY_WIFI, callerPackageName,
- caller.getUserId());
- } else {
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || canQueryAdminPolicy(caller),
- "SSID policy can only be retrieved by a device owner or "
- + "a profile owner on an organization-owned device or "
- + "an app with the QUERY_ADMIN_POLICY permission.");
- }
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller)
+ || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || canQueryAdminPolicy(caller),
+ "SSID policy can only be retrieved by a device owner or "
+ + "a profile owner on an organization-owned device or "
+ + "an app with the QUERY_ADMIN_POLICY permission.");
synchronized (getLockObject()) {
ActiveAdmin admin;
- admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceOrSystemPermissionBasedAdminLocked();
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
return admin != null ? admin.mWifiSsidPolicy : null;
}
}
@@ -22485,29 +22080,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void setWifiSsidPolicy(String callerPackageName, WifiSsidPolicy policy) {
- CallerIdentity caller;
-
- if (isPermissionCheckFlagEnabled()) {
- caller = getCallerIdentity(callerPackageName);
- } else {
- caller = getCallerIdentity();
- Preconditions.checkCallAuthorization(
- isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
- "SSID denylist can only be controlled by a device owner or "
- + "a profile owner on an organization-owned device.");
- }
+ CallerIdentity caller = getCallerIdentity();
+ Preconditions.checkCallAuthorization(
+ isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+ "SSID denylist can only be controlled by a device owner or "
+ + "a profile owner on an organization-owned device.");
boolean changed = false;
synchronized (getLockObject()) {
- ActiveAdmin admin;
- if (isPermissionCheckFlagEnabled()) {
- admin = enforcePermissionAndGetEnforcingAdmin(
- /* admin= */ null, MANAGE_DEVICE_POLICY_WIFI,
- caller.getPackageName(),
- caller.getUserId()).getActiveAdmin();
- } else {
- admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
- }
+ ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
if (!Objects.equals(policy, admin.mWifiSsidPolicy)) {
admin.mWifiSsidPolicy = policy;
changed = true;
@@ -22715,7 +22296,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private final class DevicePolicyManagementRoleObserver implements OnRoleHoldersChangedListener {
- private RoleManager mRm;
+ private final RoleManager mRm;
private final Executor mExecutor;
private final Context mContext;
@@ -22732,13 +22313,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
mDevicePolicyEngine.handleRoleChanged(roleName, user.getIdentifier());
- if (RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT.equals(roleName)) {
- handleDevicePolicyManagementRoleChange(user);
- return;
- }
- if (RoleManager.ROLE_FINANCED_DEVICE_KIOSK.equals(roleName)) {
- handleFinancedDeviceKioskRoleChange();
- return;
+ switch (roleName) {
+ case RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT ->
+ handleDevicePolicyManagementRoleChange(user);
+ case RoleManager.ROLE_FINANCED_DEVICE_KIOSK ->
+ handleFinancedDeviceKioskRoleChange();
}
}
@@ -23390,26 +22969,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
/**
* Checks if the calling process has been granted permission to apply a device policy on a
- * specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- * Returns an {@link EnforcingAdmin} for the caller.
- *
- * @param admin the component name of the admin.
- * @param callerPackageName The package name of the calling application.
- * @param permission The name of the permission being checked.
- * @param deviceAdminPolicy The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private EnforcingAdmin enforcePermissionAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, int deviceAdminPolicy, String callerPackageName, int targetUserId) {
- enforcePermission(permission, deviceAdminPolicy, callerPackageName, targetUserId);
- return getEnforcingAdminForCaller(admin, callerPackageName);
- }
-
- /**
- * Checks if the calling process has been granted permission to apply a device policy on a
* specific user. Only one permission provided in the list needs to be granted to pass this
* check.
* The given permissions will be checked along with their associated cross-user permissions if
@@ -23431,23 +22990,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
/**
- * Checks whether the calling process has been granted permission to query a device policy on
- * a specific user.
- * The given permission will be checked along with its associated cross-user permission if it
- * exists and the target user is different to the calling user.
- *
- * @param permission The name of the permission being checked.
- * @param targetUserId The userId of the user which the caller needs permission to act on.
- * @throws SecurityException if the caller has not been granted the given permission,
- * the associated cross-user permission if the caller's user is different to the target user.
- */
- private EnforcingAdmin enforceCanQueryAndGetEnforcingAdmin(@Nullable ComponentName admin,
- String permission, String callerPackageName, int targetUserId) {
- enforceCanQuery(permission, callerPackageName, targetUserId);
- return getEnforcingAdminForCaller(admin, callerPackageName);
- }
-
- /**
* Checks if the calling process has been granted permission to apply a device policy.
*
* @param callerPackageName The package name of the calling application.
@@ -23754,13 +23296,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return NOT_A_DPC;
}
- private boolean isPermissionCheckFlagEnabled() {
- return DeviceConfig.getBoolean(
- NAMESPACE_DEVICE_POLICY_MANAGER,
- PERMISSION_BASED_ACCESS_EXPERIMENT_FLAG,
- DEFAULT_VALUE_PERMISSION_BASED_ACCESS_FLAG);
- }
-
private static boolean isSetStatusBarDisabledCoexistenceEnabled() {
return false;
}
@@ -23837,58 +23372,83 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller));
}
- if (isPermissionCheckFlagEnabled()) {
+ if (Flags.setMtePolicyCoexistence()) {
enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller));
}
+
synchronized (getLockObject()) {
- ActiveAdmin admin =
+ if (Flags.setMtePolicyCoexistence()) {
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ if (flags != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mDevicePolicyEngine.setGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin,
+ new IntegerPolicyValue(flags));
+ } else {
+ mDevicePolicyEngine.removeGlobalPolicy(
+ PolicyDefinition.MEMORY_TAGGING,
+ admin);
+ }
+ } else {
+ ActiveAdmin admin =
getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
-
- if (admin != null) {
- final String memtagProperty = "arm64.memtag.bootctl";
- if (flags == DevicePolicyManager.MTE_ENABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag");
- } else if (flags == DevicePolicyManager.MTE_DISABLED) {
- mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
- } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
- mInjector.systemPropertiesSet(memtagProperty, "default");
+ if (admin != null) {
+ final String memtagProperty = "arm64.memtag.bootctl";
+ if (flags == DevicePolicyManager.MTE_ENABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag");
+ } else if (flags == DevicePolicyManager.MTE_DISABLED) {
+ mInjector.systemPropertiesSet(memtagProperty, "memtag-off");
+ } else if (flags == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ if (admin.mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ mInjector.systemPropertiesSet(memtagProperty, "default");
+ }
}
+ admin.mtePolicy = flags;
+ saveSettingsLocked(caller.getUserId());
}
- admin.mtePolicy = flags;
- saveSettingsLocked(caller.getUserId());
-
- DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
- .setInt(flags)
- .setAdmin(caller.getPackageName())
- .write();
}
+
+ DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+ .setInt(flags)
+ .setAdmin(caller.getPackageName())
+ .write();
}
}
@Override
public int getMtePolicy(String callerPackageName) {
final CallerIdentity caller = getCallerIdentity(callerPackageName);
- if (isPermissionCheckFlagEnabled()) {
+ if (Flags.setMtePolicyCoexistence()) {
enforcePermission(MANAGE_DEVICE_POLICY_MTE, caller.getPackageName(),
UserHandle.USER_ALL);
} else {
Preconditions.checkCallAuthorization(
isDefaultDeviceOwner(caller)
- || isProfileOwnerOfOrganizationOwnedDevice(caller)
- || isSystemUid(caller));
+ || isProfileOwnerOfOrganizationOwnedDevice(caller)
+ || isSystemUid(caller));
}
+
synchronized (getLockObject()) {
- ActiveAdmin admin =
+ if (Flags.setMtePolicyCoexistence()) {
+ final EnforcingAdmin admin = enforcePermissionAndGetEnforcingAdmin(null,
+ MANAGE_DEVICE_POLICY_MTE, callerPackageName, caller.getUserId());
+ final Integer policyFromAdmin = mDevicePolicyEngine.getGlobalPolicySetByAdmin(
+ PolicyDefinition.MEMORY_TAGGING, admin);
+ return (policyFromAdmin != null ? policyFromAdmin
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY);
+ } else {
+ ActiveAdmin admin =
getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked();
- return admin != null
- ? admin.mtePolicy
- : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ return admin != null
+ ? admin.mtePolicy
+ : DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
}
}
@@ -24250,6 +23810,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
Slogf.i(LOG_TAG, "Backup made: " + supervisionBackupId);
}
+ String memoryTaggingBackupId = "36.3.memory-tagging";
+ boolean memoryTaggingMigrated = maybeMigrateMemoryTaggingLocked(memoryTaggingBackupId);
+ if (memoryTaggingMigrated) {
+ Slogf.i(LOG_TAG, "Backup made: " + memoryTaggingBackupId);
+ }
+
// Additional migration steps should repeat the pattern above with a new backupId.
}
@@ -24666,7 +24232,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
|| isCallerDevicePolicyManagementRoleHolder(caller)
|| isCallerSystemSupervisionRoleHolder(caller));
return getFinancedDeviceKioskRoleHolderOnAnyUser() != null;
- };
+ }
@Override
public String getFinancedDeviceKioskRoleHolder(String callerPackageName) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index b3c8408ff54b..be4eea42f09e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -682,6 +682,19 @@ class Owners {
}
}
+ void markMemoryTaggingMigrated() {
+ synchronized (mData) {
+ mData.mMemoryTaggingMigrated = true;
+ mData.writeDeviceOwner();
+ }
+ }
+
+ boolean isMemoryTaggingMigrated() {
+ synchronized (mData) {
+ return mData.mMemoryTaggingMigrated;
+ }
+ }
+
@GuardedBy("mData")
void pushToAppOpsLocked() {
if (!mSystemReady) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
index 10e43d955fab..952bbca58f3b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/OwnersData.java
@@ -93,6 +93,9 @@ class OwnersData {
private static final String ATTR_SUSPENDED_PACKAGES_MIGRATED = "suspendedPackagesMigrated";
private static final String ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED =
"resetPasswordWithTokenMigrated";
+ private static final String ATTR_MEMORY_TAGGING_MIGRATED =
+ "memoryTaggingMigrated";
+
private static final String ATTR_MIGRATED_POST_UPGRADE = "migratedPostUpgrade";
// Internal state for the device owner package.
@@ -125,6 +128,7 @@ class OwnersData {
boolean mRequiredPasswordComplexityMigrated = false;
boolean mSuspendedPackagesMigrated = false;
boolean mResetPasswordWithTokenMigrated = false;
+ boolean mMemoryTaggingMigrated = false;
boolean mPoliciesMigratedPostUpdate = false;
@@ -424,6 +428,10 @@ class OwnersData {
out.attributeBoolean(null, ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED,
mResetPasswordWithTokenMigrated);
}
+ if (Flags.setMtePolicyCoexistence()) {
+ out.attributeBoolean(null, ATTR_MEMORY_TAGGING_MIGRATED,
+ mMemoryTaggingMigrated);
+ }
out.endTag(null, TAG_POLICY_ENGINE_MIGRATION);
}
@@ -497,7 +505,9 @@ class OwnersData {
mResetPasswordWithTokenMigrated = Flags.resetPasswordWithTokenCoexistence()
&& parser.getAttributeBoolean(null,
ATTR_RESET_PASSWORD_WITH_TOKEN_MIGRATED, false);
-
+ mMemoryTaggingMigrated = Flags.setMtePolicyCoexistence()
+ && parser.getAttributeBoolean(null,
+ ATTR_MEMORY_TAGGING_MIGRATED, false);
break;
default:
Slog.e(TAG, "Unexpected tag: " + tag);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
index f271162bbfa4..f1711f5f8c0b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java
@@ -53,6 +53,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
final class PolicyDefinition<V> {
@@ -336,6 +337,13 @@ final class PolicyDefinition<V> {
PolicyEnforcerCallbacks::noOp,
new PackageSetPolicySerializer());
+ static PolicyDefinition<Integer> MEMORY_TAGGING = new PolicyDefinition<>(
+ new NoArgsPolicyKey(
+ DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY),
+ new TopPriority<>(List.of(EnforcingAdmin.DPC_AUTHORITY)),
+ PolicyEnforcerCallbacks::setMtePolicy,
+ new IntegerPolicySerializer());
+
private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>();
private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>();
@@ -382,6 +390,8 @@ final class PolicyDefinition<V> {
PASSWORD_COMPLEXITY);
POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PACKAGES_SUSPENDED_POLICY,
PACKAGES_SUSPENDED);
+ POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.MEMORY_TAGGING_POLICY,
+ MEMORY_TAGGING);
// User Restriction Policies
USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MODIFY_ACCOUNTS, /* flags= */ 0);
@@ -504,7 +514,8 @@ final class PolicyDefinition<V> {
private final int mPolicyFlags;
// A function that accepts policy to apply, context, userId, callback arguments, and returns
// true if the policy has been enforced successfully.
- private final QuadFunction<V, Context, Integer, PolicyKey, Boolean> mPolicyEnforcerCallback;
+ private final QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ mPolicyEnforcerCallback;
private final PolicySerializer<V> mPolicySerializer;
private PolicyDefinition<V> createPolicyDefinition(PolicyKey key) {
@@ -574,7 +585,7 @@ final class PolicyDefinition<V> {
return mResolutionMechanism.resolve(adminsPolicy);
}
- boolean enforcePolicy(@Nullable V value, Context context, int userId) {
+ CompletableFuture<Boolean> enforcePolicy(@Nullable V value, Context context, int userId) {
return mPolicyEnforcerCallback.apply(value, context, userId, mPolicyKey);
}
@@ -592,7 +603,6 @@ final class PolicyDefinition<V> {
POLICY_DEFINITIONS.put(key.getIdentifier(), definition);
}
-
/**
* Callers must ensure that {@code policyType} have implemented an appropriate
* {@link Object#equals} implementation.
@@ -600,7 +610,8 @@ final class PolicyDefinition<V> {
private PolicyDefinition(
@NonNull PolicyKey key,
ResolutionMechanism<V> resolutionMechanism,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
this(key, resolutionMechanism, POLICY_FLAG_NONE, policyEnforcerCallback, policySerializer);
}
@@ -610,10 +621,11 @@ final class PolicyDefinition<V> {
* {@link Object#equals} and {@link Object#hashCode()} implementation.
*/
private PolicyDefinition(
- @NonNull PolicyKey policyKey,
+ @NonNull PolicyKey policyKey,
ResolutionMechanism<V> resolutionMechanism,
int policyFlags,
- QuadFunction<V, Context, Integer, PolicyKey, Boolean> policyEnforcerCallback,
+ QuadFunction<V, Context, Integer, PolicyKey, CompletableFuture<Boolean>>
+ policyEnforcerCallback,
PolicySerializer<V> policySerializer) {
Objects.requireNonNull(policyKey);
mPolicyKey = policyKey;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
index 4d9abf1d6be0..fdc0ec1a0471 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyEnforcerCallbacks.java
@@ -47,6 +47,7 @@ import android.os.Bundle;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.permission.AdminPermissionControlParams;
import android.permission.PermissionControllerManager;
@@ -55,6 +56,7 @@ import android.util.ArraySet;
import android.util.Slog;
import android.view.IWindowManager;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
@@ -65,6 +67,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -73,33 +76,36 @@ final class PolicyEnforcerCallbacks {
private static final String LOG_TAG = "PolicyEnforcerCallbacks";
- static <T> boolean noOp(T value, Context context, Integer userId, PolicyKey policyKey) {
- return true;
+ static <T> CompletableFuture<Boolean> noOp(T value, Context context, Integer userId,
+ PolicyKey policyKey) {
+ return AndroidFuture.completedFuture(true);
}
- static boolean setAutoTimezoneEnabled(@Nullable Boolean enabled, @NonNull Context context) {
+ static CompletableFuture<Boolean> setAutoTimezoneEnabled(@Nullable Boolean enabled,
+ @NonNull Context context) {
if (!Flags.setAutoTimeZoneEnabledCoexistence()) {
Slogf.w(LOG_TAG, "Trying to enforce setAutoTimezoneEnabled while flag is off.");
- return true;
+ return AndroidFuture.completedFuture(true);
}
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
int value = enabled != null && enabled ? 1 : 0;
- return Settings.Global.putInt(
- context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
- value);
+ return AndroidFuture.completedFuture(
+ Settings.Global.putInt(
+ context.getContentResolver(), Settings.Global.AUTO_TIME_ZONE,
+ value));
});
}
- static boolean setPermissionGrantState(
+ static CompletableFuture<Boolean> setPermissionGrantState(
@Nullable Integer grantState, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
if (!Flags.setPermissionGrantStateCoexistence()) {
Slogf.w(LOG_TAG, "Trying to enforce setPermissionGrantState while flag is off.");
- return true;
+ return AndroidFuture.completedFuture(true);
}
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePermissionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PermissionGrantStatePolicyKey, passed in policyKey is: " + policyKey);
@@ -125,12 +131,13 @@ final class PolicyEnforcerCallbacks {
.setRuntimePermissionGrantStateByDeviceAdmin(context.getPackageName(),
permissionParams, context.getMainExecutor(), callback::trigger);
try {
- return callback.await(20_000, TimeUnit.MILLISECONDS);
+ return AndroidFuture.completedFuture(
+ callback.await(20_000, TimeUnit.MILLISECONDS));
} catch (Exception e) {
// TODO: add logging
- return false;
+ return AndroidFuture.completedFuture(false);
}
- }));
+ });
}
@NonNull
@@ -149,23 +156,23 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean enforceSecurityLogging(
+ static CompletableFuture<Boolean> enforceSecurityLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceSecurityLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean enforceAuditLogging(
+ static CompletableFuture<Boolean> enforceAuditLogging(
@Nullable Boolean value, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
final var dpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
dpmi.enforceAuditLoggingPolicy(Boolean.TRUE.equals(value));
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setLockTask(
+ static CompletableFuture<Boolean> setLockTask(
@Nullable LockTaskPolicy policy, @NonNull Context context, int userId) {
List<String> packages = Collections.emptyList();
int flags = LockTaskPolicy.DEFAULT_LOCK_TASK_FLAG;
@@ -175,7 +182,7 @@ final class PolicyEnforcerCallbacks {
}
DevicePolicyManagerService.updateLockTaskPackagesLocked(context, packages, userId);
DevicePolicyManagerService.updateLockTaskFeaturesLocked(flags, userId);
- return true;
+ return AndroidFuture.completedFuture(true);
}
@@ -187,8 +194,8 @@ final class PolicyEnforcerCallbacks {
* rely on the POLICY_FLAG_SKIP_ENFORCEMENT_IF_UNCHANGED flag so DPE only invokes this callback
* when the policy is set, and not during system boot or other situations.
*/
- static boolean setApplicationRestrictions(Bundle bundle, Context context, Integer userId,
- PolicyKey policyKey) {
+ static CompletableFuture<Boolean> setApplicationRestrictions(Bundle bundle, Context context,
+ Integer userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackagePolicyKey key = (PackagePolicyKey) policyKey;
String packageName = key.getPackageName();
@@ -198,12 +205,13 @@ final class PolicyEnforcerCallbacks {
changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
context.sendBroadcastAsUser(changeIntent, UserHandle.of(userId));
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static class BlockingCallback {
private final CountDownLatch mLatch = new CountDownLatch(1);
private final AtomicReference<Boolean> mValue = new AtomicReference<>();
+
public void trigger(Boolean value) {
mValue.set(value);
mLatch.countDown();
@@ -220,7 +228,7 @@ final class PolicyEnforcerCallbacks {
// TODO: when a local policy exists for a user, this callback will be invoked for this user
// individually as well as for USER_ALL. This can be optimized by separating local and global
// enforcement in the policy engine.
- static boolean setUserControlDisabledPackages(
+ static CompletableFuture<Boolean> setUserControlDisabledPackages(
@Nullable Set<String> packages, Context context, int userId, PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
PackageManagerInternal pmi =
@@ -246,7 +254,7 @@ final class PolicyEnforcerCallbacks {
}
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
/** Handles USER_ALL expanding it into the list of all intact users. */
@@ -271,7 +279,7 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean addPersistentPreferredActivity(
+ static CompletableFuture<Boolean> addPersistentPreferredActivity(
@Nullable ComponentName preferredActivity, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -297,13 +305,13 @@ final class PolicyEnforcerCallbacks {
Slog.wtf(LOG_TAG, "Error adding/removing persistent preferred activity", re);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setUninstallBlocked(
+ static CompletableFuture<Boolean> setUninstallBlocked(
@Nullable Boolean uninstallBlocked, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -314,14 +322,14 @@ final class PolicyEnforcerCallbacks {
packageName,
uninstallBlocked != null && uninstallBlocked,
userId);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setUserRestriction(
+ static CompletableFuture<Boolean> setUserRestriction(
@Nullable Boolean enabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof UserRestrictionPolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "UserRestrictionPolicyKey, passed in policyKey is: " + policyKey);
@@ -331,14 +339,14 @@ final class PolicyEnforcerCallbacks {
UserManagerInternal userManager = LocalServices.getService(UserManagerInternal.class);
userManager.setUserRestriction(
userId, parsedKey.getRestriction(), enabled != null && enabled);
- return true;
- }));
+ return AndroidFuture.completedFuture(true);
+ });
}
- static boolean setApplicationHidden(
+ static CompletableFuture<Boolean> setApplicationHidden(
@Nullable Boolean hide, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
- return Boolean.TRUE.equals(Binder.withCleanCallingIdentity(() -> {
+ return Binder.withCleanCallingIdentity(() -> {
if (!(policyKey instanceof PackagePolicyKey)) {
throw new IllegalArgumentException("policyKey is not of type "
+ "PackagePolicyKey, passed in policyKey is: " + policyKey);
@@ -346,12 +354,13 @@ final class PolicyEnforcerCallbacks {
PackagePolicyKey parsedKey = (PackagePolicyKey) policyKey;
String packageName = Objects.requireNonNull(parsedKey.getPackageName());
IPackageManager packageManager = AppGlobals.getPackageManager();
- return packageManager.setApplicationHiddenSettingAsUser(
- packageName, hide != null && hide, userId);
- }));
+ return AndroidFuture.completedFuture(
+ packageManager.setApplicationHiddenSettingAsUser(
+ packageName, hide != null && hide, userId));
+ });
}
- static boolean setScreenCaptureDisabled(
+ static CompletableFuture<Boolean> setScreenCaptureDisabled(
@Nullable Boolean disabled, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -363,10 +372,10 @@ final class PolicyEnforcerCallbacks {
updateScreenCaptureDisabled();
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
- static boolean setContentProtectionPolicy(
+ static CompletableFuture<Boolean> setContentProtectionPolicy(
@Nullable Integer value,
@NonNull Context context,
@UserIdInt Integer userId,
@@ -378,7 +387,7 @@ final class PolicyEnforcerCallbacks {
cacheImpl.setContentProtectionPolicy(userId, value);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void updateScreenCaptureDisabled() {
@@ -393,7 +402,7 @@ final class PolicyEnforcerCallbacks {
});
}
- static boolean setPersonalAppsSuspended(
+ static CompletableFuture<Boolean> setPersonalAppsSuspended(
@Nullable Boolean suspended, @NonNull Context context, int userId,
@NonNull PolicyKey policyKey) {
Binder.withCleanCallingIdentity(() -> {
@@ -404,7 +413,7 @@ final class PolicyEnforcerCallbacks {
.unsuspendAdminSuspendedPackages(userId);
}
});
- return true;
+ return AndroidFuture.completedFuture(true);
}
private static void suspendPersonalAppsInPackageManager(Context context, int userId) {
@@ -418,13 +427,53 @@ final class PolicyEnforcerCallbacks {
}
}
- static boolean setUsbDataSignalingEnabled(@Nullable Boolean value, @NonNull Context context) {
+ static CompletableFuture<Boolean> setUsbDataSignalingEnabled(@Nullable Boolean value,
+ @NonNull Context context) {
return Binder.withCleanCallingIdentity(() -> {
Objects.requireNonNull(context);
boolean enabled = value == null || value;
DevicePolicyManagerService.updateUsbDataSignal(context, enabled);
- return true;
+ return AndroidFuture.completedFuture(true);
});
}
+
+ static CompletableFuture<Boolean> setMtePolicy(
+ @Nullable Integer mtePolicy, @NonNull Context context, int userId,
+ @NonNull PolicyKey policyKey) {
+ if (mtePolicy == null) {
+ mtePolicy = DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
+ }
+ final Set<Integer> allowedModes =
+ Set.of(
+ DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY,
+ DevicePolicyManager.MTE_DISABLED,
+ DevicePolicyManager.MTE_ENABLED);
+ if (!allowedModes.contains(mtePolicy)) {
+ Slog.wtf(LOG_TAG, "MTE policy is not a known one: " + mtePolicy);
+ return AndroidFuture.completedFuture(false);
+ }
+
+ final String mteDpmSystemProperty =
+ "ro.arm64.memtag.bootctl_device_policy_manager";
+ final String mteSettingsSystemProperty =
+ "ro.arm64.memtag.bootctl_settings_toggle";
+ final String mteControlProperty = "arm64.memtag.bootctl";
+
+ final boolean isAvailable = SystemProperties.getBoolean(mteDpmSystemProperty,
+ SystemProperties.getBoolean(mteSettingsSystemProperty, false));
+ if (!isAvailable) {
+ return AndroidFuture.completedFuture(false);
+ }
+
+ if (mtePolicy == DevicePolicyManager.MTE_ENABLED) {
+ SystemProperties.set(mteControlProperty, "memtag");
+ } else if (mtePolicy == DevicePolicyManager.MTE_DISABLED) {
+ SystemProperties.set(mteControlProperty, "memtag-off");
+ } else if (mtePolicy == DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
+ SystemProperties.set(mteControlProperty, "default");
+ }
+
+ return AndroidFuture.completedFuture(true);
+ }
}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
index a804f24acc8f..c30ab738b098 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodSubtypeSwitchingControllerTest.java
@@ -101,13 +101,13 @@ public final class InputMethodSubtypeSwitchingControllerTest {
TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
TEST_FORCE_DEFAULT, supportsSwitchingToNextInputMethod, TEST_IS_VR_IME);
if (subtypes == null) {
- items.add(new ImeSubtypeListItem(imeName, null /* variableName */, imi,
- NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
+ items.add(new ImeSubtypeListItem(imeName, null /* subtypeName */, null /* layoutName */,
+ imi, NOT_A_SUBTYPE_INDEX, null, SYSTEM_LOCALE));
} else {
for (int i = 0; i < subtypes.size(); ++i) {
final String subtypeLocale = subtypeLocales.get(i);
- items.add(new ImeSubtypeListItem(imeName, subtypeLocale, imi, i, subtypeLocale,
- SYSTEM_LOCALE));
+ items.add(new ImeSubtypeListItem(imeName, subtypeLocale, null /* layoutName */,
+ imi, i, subtypeLocale, SYSTEM_LOCALE));
}
}
}
@@ -138,8 +138,8 @@ public final class InputMethodSubtypeSwitchingControllerTest {
final InputMethodInfo imi = new InputMethodInfo(ri, TEST_IS_AUX_IME,
TEST_SETTING_ACTIVITY_NAME, subtypes, TEST_IS_DEFAULT_RES_ID,
TEST_FORCE_DEFAULT, true /* supportsSwitchingToNextInputMethod */, TEST_IS_VR_IME);
- return new ImeSubtypeListItem(imeName, subtypeName, imi, subtypeIndex, subtypeLocale,
- SYSTEM_LOCALE);
+ return new ImeSubtypeListItem(imeName, subtypeName, null /* layoutName */, imi,
+ subtypeIndex, subtypeLocale, SYSTEM_LOCALE);
}
@NonNull
diff --git a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
index 3ac7fb0dbe53..e0b0fec380dd 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/VirtualDisplayAdapterTest.java
@@ -335,6 +335,10 @@ public class VirtualDisplayAdapterTest {
@Override
public void onStopped() {
}
+
+ @Override
+ public void onRequestedBrightnessChanged(float brightness) {
+ }
};
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/OWNERS b/services/tests/mockingservicestests/src/com/android/server/OWNERS
index dc5cb8d6bdf5..0c9f70ca96b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/OWNERS
+++ b/services/tests/mockingservicestests/src/com/android/server/OWNERS
@@ -1,6 +1,6 @@
per-file *Alarm* = file:/apex/jobscheduler/OWNERS
per-file *AppStateTracker* = file:/apex/jobscheduler/OWNERS
-per-file *DeviceIdleController* = file:/apex/jobscheduler/OWNERS
+per-file *DeviceIdleController* = file:/apex/jobscheduler/DEVICE_IDLE_OWNERS
per-file SensitiveContentProtectionManagerService* = file:/core/java/android/permission/OWNERS
per-file RescuePartyTest.java = file:/packages/CrashRecovery/OWNERS
per-file *Storage* = file:/core/java/android/os/storage/OWNERS
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index b0053581963a..4a1315583ad4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -60,6 +60,7 @@ import static com.android.server.am.ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_AP
import static com.android.server.am.ProcessList.PERSISTENT_PROC_ADJ;
import static com.android.server.am.ProcessList.PERSISTENT_SERVICE_ADJ;
import static com.android.server.am.ProcessList.PREVIOUS_APP_ADJ;
+import static com.android.server.am.ProcessList.PREVIOUS_APP_MAX_ADJ;
import static com.android.server.am.ProcessList.SCHED_GROUP_BACKGROUND;
import static com.android.server.am.ProcessList.SCHED_GROUP_DEFAULT;
import static com.android.server.am.ProcessList.SCHED_GROUP_FOREGROUND_WINDOW;
@@ -129,6 +130,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
/**
* Test class for {@link OomAdjuster}.
@@ -899,8 +901,25 @@ public class MockingOomAdjusterTests {
@SuppressWarnings("GuardedBy")
@Test
+ public void testUpdateOomAdj_DoPending_PreviousApp() {
+ testUpdateOomAdj_PreviousApp(apps -> {
+ for (ProcessRecord app : apps) {
+ mProcessStateController.enqueueUpdateTarget(app);
+ }
+ mProcessStateController.runPendingUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ @SuppressWarnings("GuardedBy")
+ @Test
public void testUpdateOomAdj_DoAll_PreviousApp() {
- final int numberOfApps = 15;
+ testUpdateOomAdj_PreviousApp(apps -> {
+ mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
+ });
+ }
+
+ private void testUpdateOomAdj_PreviousApp(Consumer<ProcessRecord[]> updater) {
+ final int numberOfApps = 105;
final ProcessRecord[] apps = new ProcessRecord[numberOfApps];
for (int i = 0; i < numberOfApps; i++) {
apps[i] = spy(makeDefaultProcessRecord(MOCKAPP_PID + i, MOCKAPP_UID + i,
@@ -911,10 +930,11 @@ public class MockingOomAdjusterTests {
}
setWakefulness(PowerManagerInternal.WAKEFULNESS_AWAKE);
setProcessesToLru(apps);
- mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
-
+ updater.accept(apps);
for (int i = 0; i < numberOfApps; i++) {
- assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ final int mruIndex = numberOfApps - i - 1;
+ final int expectedAdj = Math.min(PREVIOUS_APP_ADJ + mruIndex, PREVIOUS_APP_MAX_ADJ);
+ assertProcStates(apps[i], PROCESS_STATE_LAST_ACTIVITY, expectedAdj,
SCHED_GROUP_BACKGROUND, "previous");
}
@@ -3184,7 +3204,8 @@ public class MockingOomAdjusterTests {
setProcessesToLru(app1, app2);
mProcessStateController.runFullUpdate(OOM_ADJ_REASON_NONE);
- assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
+ assertProcStates(app1, PROCESS_STATE_LAST_ACTIVITY,
+ PREVIOUS_APP_ADJ + (Flags.oomadjusterPrevLaddering() ? 1 : 0),
SCHED_GROUP_BACKGROUND, "recent-provider");
assertProcStates(app2, PROCESS_STATE_LAST_ACTIVITY, PREVIOUS_APP_ADJ,
SCHED_GROUP_BACKGROUND, "recent-provider");
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
index 197342874b2a..f7c2e8b72d6b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsLegacyRestrictionsTest.java
@@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_FINE_LOCATION;
import static org.junit.Assert.assertEquals;
+import android.app.PropertyInvalidatedCache;
import android.content.Context;
import android.os.Handler;
@@ -63,6 +64,7 @@ public class AppOpsLegacyRestrictionsTest {
@Before
public void setUp() {
+ PropertyInvalidatedCache.disableForTestMode();
mSession = ExtendedMockito.mockitoSession()
.initMocks(this)
.strictness(Strictness.LENIENT)
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 4e1f741b1398..dd7ce21e3628 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -2351,6 +2351,7 @@ public class JobSchedulerServiceTest {
/** Tests that jobs are removed from the pending list if the user stops the app. */
@Test
+ @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
public void testUserStopRemovesPending() {
spyOn(mService);
@@ -2402,6 +2403,60 @@ public class JobSchedulerServiceTest {
assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
}
+ /** Tests that jobs are removed from the pending list if the user stops the app. */
+ @Test
+ @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
+ public void testUserStopRemovesPending_withPendingJobReasonsApi() {
+ spyOn(mService);
+
+ JobStatus job1a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 1, "pkg1");
+ JobStatus job1b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 1, "pkg1");
+ JobStatus job2a = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(1), 2, "pkg2");
+ JobStatus job2b = createJobStatus("testUserStopRemovesPending",
+ createJobInfo(2), 2, "pkg2");
+ doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
+ doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
+ doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
+
+ mService.getPendingJobQueue().clear();
+ mService.getPendingJobQueue().add(job1a);
+ mService.getPendingJobQueue().add(job1b);
+ mService.getPendingJobQueue().add(job2a);
+ mService.getPendingJobQueue().add(job2b);
+ mService.getJobStore().add(job1a);
+ mService.getJobStore().add(job1b);
+ mService.getJobStore().add(job2a);
+ mService.getJobStore().add(job2b);
+
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
+ assertEquals(4, mService.getPendingJobQueue().size());
+ assertTrue(mService.getPendingJobQueue().contains(job1a));
+ assertTrue(mService.getPendingJobQueue().contains(job1b));
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
+ assertEquals(2, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]);
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]);
+ assertTrue(mService.getPendingJobQueue().contains(job2a));
+ assertTrue(mService.getPendingJobQueue().contains(job2b));
+
+ mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
+ assertEquals(0, mService.getPendingJobQueue().size());
+ assertFalse(mService.getPendingJobQueue().contains(job1a));
+ assertFalse(mService.getPendingJobQueue().contains(job1b));
+ assertFalse(mService.getPendingJobQueue().contains(job2a));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]);
+ assertFalse(mService.getPendingJobQueue().contains(job2b));
+ assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]);
+ }
+
/**
* Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link
* JobRestriction} registered.
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 1cba3c574543..8a10040f986f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -195,12 +195,12 @@ public final class UserManagerServiceTest {
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
mockIsLowRamDevice(false);
- // Called when getting boot user. config_bootToHeadlessSystemUser is false by default.
+ // Called when getting boot user. config_bootToHeadlessSystemUser is 0 by default.
mSpyResources = spy(mSpiedContext.getResources());
when(mSpiedContext.getResources()).thenReturn(mSpyResources);
- doReturn(false)
+ doReturn(0)
.when(mSpyResources)
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
// Must construct UserManagerService in the UiThread
mTestDir = new File(mRealContext.getDataDir(), "umstest");
@@ -859,15 +859,50 @@ public final class UserManagerServiceTest {
}
@Test
- public void testGetBootUser_enableBootToHeadlessSystemUser() {
+ public void testGetBootUser_Headless_BootToSystemUserWhenDeviceIsProvisioned() {
setSystemUserHeadless(true);
- doReturn(true)
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ mockProvisionedDevice(true);
+ doReturn(1)
.when(mSpyResources)
- .getBoolean(com.android.internal.R.bool.config_bootToHeadlessSystemUser);
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
assertThat(mUms.getBootUser()).isEqualTo(UserHandle.USER_SYSTEM);
}
+ @Test
+ public void testGetBootUser_Headless_BootToFirstSwitchableFullUserWhenDeviceNotProvisioned() {
+ setSystemUserHeadless(true);
+ addUser(USER_ID);
+ addUser(OTHER_USER_ID);
+ mockProvisionedDevice(false);
+ doReturn(1)
+ .when(mSpyResources)
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+ // Even if the headless system user switchable flag is true, the boot user should be the
+ // first switchable full user.
+ doReturn(true)
+ .when(mSpyResources)
+ .getBoolean(com.android.internal.R.bool.config_canSwitchToHeadlessSystemUser);
+
+ assertThat(mUms.getBootUser()).isEqualTo(USER_ID);
+ }
+
+ @Test
+ public void testGetBootUser_Headless_ThrowsIfBootFailsNoFullUserWhenDeviceNotProvisioned()
+ throws Exception {
+ setSystemUserHeadless(true);
+ removeNonSystemUsers();
+ mockProvisionedDevice(false);
+ doReturn(1)
+ .when(mSpyResources)
+ .getInteger(com.android.internal.R.integer.config_hsumBootStrategy);
+
+ assertThrows(ServiceSpecificException.class,
+ () -> mUms.getBootUser());
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -935,6 +970,11 @@ public final class UserManagerServiceTest {
any(), eq(android.provider.Settings.Global.USER_SWITCHER_ENABLED), anyInt()));
}
+ private void mockProvisionedDevice(boolean isProvisionedDevice) {
+ doReturn(isProvisionedDevice ? 1 : 0).when(() -> Settings.Global.getInt(
+ any(), eq(android.provider.Settings.Global.DEVICE_PROVISIONED), anyInt()));
+ }
+
private void mockIsLowRamDevice(boolean isLowRamDevice) {
doReturn(isLowRamDevice).when(ActivityManager::isLowRamDeviceStatic);
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
index 648da6530eb0..4e29e74651b6 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerGroupTest.java
@@ -49,17 +49,23 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
import android.os.PowerManager;
import android.os.PowerSaveState;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.Display;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.LatencyTracker;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.power.feature.PowerManagerFlags;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -73,6 +79,9 @@ import org.mockito.MockitoAnnotations;
public class PowerGroupTest {
private static final int GROUP_ID = 0;
+ private static final int NON_DEFAULT_GROUP_ID = 1;
+ private static final int NON_DEFAULT_DISPLAY_ID = 2;
+ private static final int VIRTUAL_DEVICE_ID = 3;
private static final int UID = 11;
private static final long TIMESTAMP_CREATE = 1;
private static final long TIMESTAMP1 = 999;
@@ -87,10 +96,16 @@ public class PowerGroupTest {
private static final LatencyTracker LATENCY_TRACKER = LatencyTracker.getInstance(
InstrumentationRegistry.getInstrumentation().getContext());
+ private static final long DEFAULT_TIMEOUT = 1234L;
+
+ @Rule
+ public SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private PowerGroup mPowerGroup;
@Mock private PowerGroup.PowerGroupListener mWakefulnessCallbackMock;
@Mock private Notifier mNotifier;
@Mock private DisplayManagerInternal mDisplayManagerInternal;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternal;
@Mock private PowerManagerFlags mFeatureFlags;
@@ -740,4 +755,111 @@ public class PowerGroupTest {
assertThat(displayPowerRequest.screenLowPowerBrightnessFactor).isWithin(PRECISION).of(
brightnessFactor);
}
+
+ @Test
+ public void testTimeoutsOverride_defaultGroup_noOverride() {
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_noVdm_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, null);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_notValidVirtualDeviceId_noOverride() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(Context.DEVICE_ID_DEFAULT);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(Context.DEVICE_ID_DEFAULT))
+ .thenReturn(false);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(DEFAULT_TIMEOUT);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverride_validVirtualDeviceId_timeoutsAreOverridden() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 3;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 5;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(dimDurationOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
+
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @Test
+ public void testTimeoutsOverrides_dimDurationIsCapped() {
+ LocalServices.removeServiceForTest(VirtualDeviceManagerInternal.class);
+ LocalServices.addService(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternal);
+
+ final long dimDurationOverride = DEFAULT_TIMEOUT * 5;
+ final long screenOffTimeoutOverride = DEFAULT_TIMEOUT * 3;
+
+ when(mDisplayManagerInternal.getDisplayIdsForGroup(NON_DEFAULT_GROUP_ID))
+ .thenReturn(new int[] {NON_DEFAULT_DISPLAY_ID});
+ when(mVirtualDeviceManagerInternal.getDeviceIdForDisplayId(NON_DEFAULT_DISPLAY_ID))
+ .thenReturn(VIRTUAL_DEVICE_ID);
+ when(mVirtualDeviceManagerInternal.isValidVirtualDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternal.getDimDurationMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(dimDurationOverride);
+ when(mVirtualDeviceManagerInternal.getScreenOffTimeoutMillisForDeviceId(VIRTUAL_DEVICE_ID))
+ .thenReturn(screenOffTimeoutOverride);
+
+ mPowerGroup = new PowerGroup(NON_DEFAULT_GROUP_ID, mWakefulnessCallbackMock, mNotifier,
+ mDisplayManagerInternal, WAKEFULNESS_AWAKE, /* ready= */ true,
+ /* supportsSandman= */ true, TIMESTAMP_CREATE, mFeatureFlags);
+
+ assertThat(mPowerGroup.getScreenDimDurationOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ assertThat(mPowerGroup.getScreenOffTimeoutOverrideLocked(DEFAULT_TIMEOUT))
+ .isEqualTo(screenOffTimeoutOverride);
+ }
}
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index b10200daeeb4..359cf6376239 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -85,6 +85,7 @@ import android.os.PowerManagerInternal;
import android.os.PowerSaveState;
import android.os.UserHandle;
import android.os.test.TestLooper;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -102,6 +103,7 @@ import com.android.internal.foldables.FoldGracePeriodProvider;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
@@ -167,6 +169,7 @@ public class PowerManagerServiceTest {
@Mock private BatteryManagerInternal mBatteryManagerInternalMock;
@Mock private ActivityManagerInternal mActivityManagerInternalMock;
@Mock private AttentionManagerInternal mAttentionManagerInternalMock;
+ @Mock private VirtualDeviceManagerInternal mVirtualDeviceManagerInternalMock;
@Mock private DreamManagerInternal mDreamManagerInternalMock;
@Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
@Mock private FoldGracePeriodProvider mFoldGracePeriodProvider;
@@ -246,6 +249,7 @@ public class PowerManagerServiceTest {
addLocalServiceMock(ActivityManagerInternal.class, mActivityManagerInternalMock);
addLocalServiceMock(AttentionManagerInternal.class, mAttentionManagerInternalMock);
addLocalServiceMock(DreamManagerInternal.class, mDreamManagerInternalMock);
+ addLocalServiceMock(VirtualDeviceManagerInternal.class, mVirtualDeviceManagerInternalMock);
mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResourcesSpy = spy(mContextSpy.getResources());
@@ -1200,6 +1204,59 @@ public class PowerManagerServiceTest {
assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_DREAMING);
}
+ @EnableFlags(android.companion.virtualdevice.flags.Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
+ @SuppressWarnings("GuardedBy")
+ @Test
+ public void testNonDefaultDisplayGroupWithCustomTimeout_afterTimeout_goesToDozing() {
+ final int nonDefaultDisplayGroupId = Display.DEFAULT_DISPLAY_GROUP + 1;
+ final int nonDefaultDisplayId = Display.DEFAULT_DISPLAY + 2;
+ final int virtualDeviceId = Context.DEVICE_ID_DEFAULT + 3;
+ final AtomicReference<DisplayManagerInternal.DisplayGroupListener> listener =
+ new AtomicReference<>();
+ doAnswer((Answer<Void>) invocation -> {
+ listener.set(invocation.getArgument(0));
+ return null;
+ }).when(mDisplayManagerInternalMock).registerDisplayGroupListener(any());
+ final DisplayInfo info = new DisplayInfo();
+ info.displayGroupId = nonDefaultDisplayGroupId;
+ when(mDisplayManagerInternalMock.getDisplayInfo(nonDefaultDisplayId)).thenReturn(info);
+ when(mDisplayManagerInternalMock.getDisplayIdsForGroup(nonDefaultDisplayGroupId))
+ .thenReturn(new int[] {nonDefaultDisplayId});
+ when(mVirtualDeviceManagerInternalMock.getDeviceIdForDisplayId(nonDefaultDisplayId))
+ .thenReturn(virtualDeviceId);
+ when(mVirtualDeviceManagerInternalMock.isValidVirtualDeviceId(virtualDeviceId))
+ .thenReturn(true);
+ when(mVirtualDeviceManagerInternalMock
+ .getScreenOffTimeoutMillisForDeviceId(virtualDeviceId))
+ .thenReturn(20000L);
+
+ setMinimumScreenOffTimeoutConfig(10000);
+ createService();
+ startSystem();
+ listener.get().onDisplayGroupAdded(nonDefaultDisplayGroupId);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ // The default timeout is 10s, the custom group timeout is 20s.
+
+ advanceTime(15000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_AWAKE);
+
+ advanceTime(10000);
+
+ assertThat(mService.getWakefulnessLocked(Display.DEFAULT_DISPLAY_GROUP))
+ .isEqualTo(WAKEFULNESS_ASLEEP);
+ assertThat(mService.getWakefulnessLocked(nonDefaultDisplayGroupId))
+ .isEqualTo(WAKEFULNESS_DOZING);
+ }
+
@SuppressWarnings("GuardedBy")
@Test
public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
@@ -3348,7 +3405,8 @@ public class PowerManagerServiceTest {
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY,
null /* callback */);
}
- assertThat(mService.getScreenOffTimeoutOverrideLocked(screenTimeout, screenDimTimeout))
+ assertThat(mService.getDefaultGroupScreenOffTimeoutOverrideLocked(screenTimeout,
+ screenDimTimeout))
.isEqualTo(expect[2]);
if (acquireWakeLock) {
mService.getBinderServiceInstance().releaseWakeLock(token, 0);
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index 0a1fc3184fca..f02a389a160e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -438,10 +438,7 @@ public class BatteryStatsImplTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong());
@@ -451,20 +448,13 @@ public class BatteryStatsImplTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer deltaContainer =
- invocation.getArgument(3);
-
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
- if (deltaContainer != null) {
- deltaContainer.setValues(delta);
- }
+ long[] deltaOut = invocation.getArgument(3);
+ counter.updateValues(cpuTimes, timestampMs);
+ System.arraycopy(delta, 0, deltaOut, 0, delta.length);
return null;
}).when(mKernelSingleUidTimeReader).addDelta(eq(testUid),
any(LongArrayMultiStateCounter.class), anyLong(),
- any(LongArrayMultiStateCounter.LongArrayContainer.class));
+ any(long[].class));
}
@Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 71a65c85d9e1..4cea72835fba 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -394,10 +394,7 @@ public class CpuPowerCalculatorTest {
doAnswer(invocation -> {
LongArrayMultiStateCounter counter = invocation.getArgument(1);
long timestampMs = invocation.getArgument(2);
- LongArrayMultiStateCounter.LongArrayContainer container =
- new LongArrayMultiStateCounter.LongArrayContainer(NUM_CPU_FREQS);
- container.setValues(cpuTimes);
- counter.updateValues(container, timestampMs);
+ counter.updateValues(cpuTimes, timestampMs);
return null;
}).when(mMockKernelSingleUidTimeReader).addDelta(eq(uid),
any(LongArrayMultiStateCounter.class), anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
index c4b3c149bd8d..5d7ffe91e67d 100644
--- a/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
+++ b/services/tests/servicestests/src/com/android/server/appop/AppOpsRecentAccessPersistenceTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
+import android.app.PropertyInvalidatedCache;
import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.os.FileUtils;
@@ -69,6 +70,7 @@ public class AppOpsRecentAccessPersistenceTest {
@Before
public void setUp() {
+ PropertyInvalidatedCache.disableForTestMode();
when(mAppOpCheckingService.addAppOpsModeChangedListener(any())).thenReturn(true);
LocalServices.addService(AppOpsCheckingServiceInterface.class, mAppOpCheckingService);
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 e3868089b21f..727d1b59646a 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
@@ -64,6 +64,7 @@ import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
import android.companion.virtual.sensor.VirtualSensorCallback;
import android.companion.virtual.sensor.VirtualSensorConfig;
+import android.companion.virtualdevice.flags.Flags;
import android.content.AttributionSource;
import android.content.ComponentName;
import android.content.Context;
@@ -103,6 +104,7 @@ import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.WorkSource;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
@@ -999,6 +1001,7 @@ public class VirtualDeviceManagerServiceTest {
nullable(String.class), anyInt(), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayCreatedLocked_wakeLockIsAcquired() throws RemoteException {
verify(mIPowerManagerMock, never()).acquireWakeLock(any(Binder.class), anyInt(),
@@ -1010,6 +1013,7 @@ public class VirtualDeviceManagerServiceTest {
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayCreatedLocked_duplicateCalls_onlyOneWakeLockIsAcquired()
throws RemoteException {
@@ -1022,6 +1026,7 @@ public class VirtualDeviceManagerServiceTest {
nullable(String.class), eq(DISPLAY_ID_1), eq(null));
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
@@ -1037,6 +1042,7 @@ public class VirtualDeviceManagerServiceTest {
verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
}
+ @DisableFlags(Flags.FLAG_DEVICE_AWARE_DISPLAY_POWER)
@Test
public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1, Display.FLAG_TRUSTED);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index c741c6c041a2..077bb03c8359 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -2562,7 +2562,13 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
// User interacted with the DUT, so the device will not go to standby.
- skipActiveSourceLostUi(0, true, true);
+ mHdmiControlService.oneTouchPlay(new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ }
+ });
+ mTestLooper.dispatchAll();
+
assertThat(mIsOnActiveSourceLostPopupActive).isFalse();
assertThat(mPowerManager.isInteractive()).isTrue();
assertThat(mNativeWrapper.getResultMessages().contains(activeSourceFromPlayback)).isTrue();
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
deleted file mode 100644
index 9ed2e88bd6a2..000000000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ /dev/null
@@ -1,914 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
-import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
-import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.DEFAULT_FORMAT_VERSION;
-import static com.android.server.integrity.model.ComponentBitSize.EFFECT_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.FORMAT_VERSION_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.KEY_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
-import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
-import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
-import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
-import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.INDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.serializer.RuleBinarySerializer.NONINDEXED_RULE_SIZE_LIMIT;
-import static com.android.server.integrity.utils.TestUtils.getBits;
-import static com.android.server.integrity.utils.TestUtils.getBytes;
-import static com.android.server.integrity.utils.TestUtils.getValueBits;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.IntegrityUtils;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.ByteArrayOutputStream;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Optional;
-
-@RunWith(JUnit4.class)
-public class RuleBinarySerializerTest {
-
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERT = "installer_cert";
-
- private static final String COMPOUND_FORMULA_START_BITS =
- getBits(COMPOUND_FORMULA_START, SEPARATOR_BITS);
- private static final String COMPOUND_FORMULA_END_BITS =
- getBits(COMPOUND_FORMULA_END, SEPARATOR_BITS);
- private static final String ATOMIC_FORMULA_START_BITS =
- getBits(ATOMIC_FORMULA_START, SEPARATOR_BITS);
-
- private static final String NOT = getBits(CompoundFormula.NOT, CONNECTOR_BITS);
- private static final String AND = getBits(CompoundFormula.AND, CONNECTOR_BITS);
- private static final String OR = getBits(CompoundFormula.OR, CONNECTOR_BITS);
-
- private static final String PACKAGE_NAME = getBits(AtomicFormula.PACKAGE_NAME, KEY_BITS);
- private static final String APP_CERTIFICATE = getBits(AtomicFormula.APP_CERTIFICATE, KEY_BITS);
- private static final String INSTALLER_NAME = getBits(AtomicFormula.INSTALLER_NAME, KEY_BITS);
- private static final String INSTALLER_CERTIFICATE =
- getBits(AtomicFormula.INSTALLER_CERTIFICATE, KEY_BITS);
- private static final String VERSION_CODE = getBits(AtomicFormula.VERSION_CODE, KEY_BITS);
- private static final String PRE_INSTALLED = getBits(AtomicFormula.PRE_INSTALLED, KEY_BITS);
-
- private static final String EQ = getBits(AtomicFormula.EQ, OPERATOR_BITS);
-
- private static final String IS_NOT_HASHED = "0";
- private static final String IS_HASHED = "1";
-
- private static final String DENY = getBits(Rule.DENY, EFFECT_BITS);
-
- private static final String START_BIT = "1";
- private static final String END_BIT = "1";
-
- private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
- getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
-
- private static final String SERIALIZED_START_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(START_INDEXING_KEY);
- private static final String SERIALIZED_END_INDEXING_KEY =
- IS_NOT_HASHED
- + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
- + getValueBits(END_INDEXING_KEY);
-
- @Test
- public void testBinaryString_serializeNullRules() {
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Null rules cannot be serialized.",
- () -> binarySerializer.serialize(null, /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_emptyRules() throws Exception {
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- binarySerializer.serialize(
- Collections.emptyList(),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String serializedIndexingBytes =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- byte[] expectedIndexingBytes =
- getBytes(
- serializedIndexingBytes
- + serializedIndexingBytes
- + serializedIndexingBytes);
- expectedIndexingOutputStream.write(expectedIndexingBytes);
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryStream_serializeValidCompoundFormula() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
-
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
- expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- expectedRuleOutputStream.write(getBytes(expectedBits));
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedRuleOutputStream.toByteArray());
-
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
- String expectedIndexingBitsForIndexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
- String expectedIndexingBitsForUnindexed =
- SERIALIZED_START_INDEXING_KEY
- + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
- + SERIALIZED_END_INDEXING_KEY
- + getBits(
- DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
- /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBitsForIndexed
- + expectedIndexingBitsForIndexed
- + expectedIndexingBitsForUnindexed));
-
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_notConnector() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Collections.singletonList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + NOT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_andConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidCompoundFormula_orConnector() throws Exception {
- String packageName = "com.test.app";
- String appCertificate = "test_cert";
- Rule rule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.OR,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- appCertificate,
- /* isHashedValue= */ false))),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + OR
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_stringValue() throws Exception {
- String packageName = "com.test.app";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_hashedValue() throws Exception {
- String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
- Rule rule =
- new Rule(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- IntegrityUtils.getHexDigest(
- appCertificate.getBytes(StandardCharsets.UTF_8)),
- /* isHashedValue= */ true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_integerValue() throws Exception {
- long versionCode = 1;
- Rule rule =
- new Rule(
- new AtomicFormula.LongAtomicFormula(
- AtomicFormula.VERSION_CODE, AtomicFormula.EQ, versionCode),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + VERSION_CODE
- + EQ
- + getBits(versionCode, /* numOfBits= */ 64)
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeValidAtomicFormula_booleanValue() throws Exception {
- String preInstalled = "1";
- Rule rule =
- new Rule(
- new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
- Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits =
- START_BIT
- + ATOMIC_FORMULA_START_BITS
- + PRE_INSTALLED
- + EQ
- + preInstalled
- + DENY
- + END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.singletonList(rule), /* formatVersion= */ Optional.empty());
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_serializeInvalidFormulaType() throws Exception {
- IntegrityFormula invalidFormula = getInvalidFormula();
- Rule rule = new Rule(invalidFormula, Rule.DENY);
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () ->
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty()));
- }
-
- @Test
- public void testBinaryString_serializeFormatVersion() throws Exception {
- int formatVersion = 1;
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- String expectedBits = getBits(formatVersion, FORMAT_VERSION_BITS);
- byte[] expectedRules = getBytes(expectedBits);
-
- byte[] actualRules =
- binarySerializer.serialize(
- Collections.emptyList(), /* formatVersion= */ Optional.of(formatVersion));
-
- assertThat(actualRules).isEqualTo(expectedRules);
- }
-
- @Test
- public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
- int ruleCount = 225;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
- // and 225 non-indexed rules..
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream);
-
- // Verify the rules file and index files.
- ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
-
- expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
-
- String expectedIndexingBytesForPackageNameIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String packageName = String.format("%s%04d", packagePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForPackageNameIndexed +=
- IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageName));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForPackageNameIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForAppCertificateIndexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
- if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
- expectedIndexingBytesForAppCertificateIndexed +=
- IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + getBits(totalBytesWritten, /* numOfBits= */ 32);
- }
-
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCertificate));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForAppCertificateIndexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
-
- String expectedIndexingBytesForUnindexed =
- SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- for (int count = 0; count < ruleCount; count++) {
- byte[] bytesForPackage =
- getBytes(
- getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String.format("%s%04d", installerNamePrefix, count)));
- expectedOrderedRuleOutputStream.write(bytesForPackage);
- totalBytesWritten += bytesForPackage.length;
- }
- expectedIndexingBytesForUnindexed +=
- SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
- expectedIndexingOutputStream.write(
- getBytes(
- expectedIndexingBytesForPackageNameIndexed
- + expectedIndexingBytesForAppCertificateIndexed
- + expectedIndexingBytesForUnindexed));
-
- assertThat(ruleOutputStream.toByteArray())
- .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
- assertThat(indexingOutputStream.toByteArray())
- .isEqualTo(expectedIndexingOutputStream.toByteArray());
- }
-
- @Test
- public void testBinaryString_totalRuleSizeLimitReached() {
- int ruleCount = INDEXED_RULE_SIZE_LIMIT - 1;
- String packagePrefix = "package.name.";
- String appCertificatePrefix = "app.cert.";
- String installerNamePrefix = "installer.";
-
- // Create the rule set with more rules than the system can handle in total.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
- for (int count = 0; count < ruleCount; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT - 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyPackageNameIndexedRules() {
- String packagePrefix = "package.name.";
-
- // Create a rule set with too many package name indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithPackageNameAndSampleInstallerName(
- String.format("%s%04d", packagePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyAppCertificateIndexedRules() {
- String appCertificatePrefix = "app.cert.";
-
- // Create a rule set with too many app certificate indexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < INDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getRuleWithAppCertificateAndSampleInstallerName(
- String.format("%s%04d", appCertificatePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- @Test
- public void testBinaryString_tooManyNonIndexedRules() {
- String installerNamePrefix = "installer.";
-
- // Create a rule set with too many unindexed rules.
- List<Rule> ruleList = new ArrayList();
- for (int count = 0; count < NONINDEXED_RULE_SIZE_LIMIT + 1; count++) {
- ruleList.add(
- getNonIndexedRuleWithInstallerName(
- String.format("%s%04d", installerNamePrefix, count)));
- }
-
- // Serialize the rules.
- ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
- ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
- RuleSerializer binarySerializer = new RuleBinarySerializer();
-
- assertExpectException(
- RuleSerializeException.class,
- "Too many rules provided in the indexing group.",
- () ->
- binarySerializer.serialize(
- ruleList,
- /* formatVersion= */ Optional.empty(),
- ruleOutputStream,
- indexingOutputStream));
- }
-
- private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- String packageName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + PACKAGE_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(packageName.length(), VALUE_SIZE_BITS)
- + getValueBits(packageName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getRuleWithAppCertificateAndSampleInstallerName(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- String appCertificate) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + APP_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(appCertificate.length(), VALUE_SIZE_BITS)
- + getValueBits(appCertificate)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private Rule getNonIndexedRuleWithInstallerName(String installerName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- installerName,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERT,
- /* isHashedValue= */ false))),
- Rule.DENY);
- }
-
- private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
- String installerName) {
- return START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(installerName.length(), VALUE_SIZE_BITS)
- + getValueBits(installerName)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_CERT)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- }
-
- private static IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return 0;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
deleted file mode 100644
index 6dccdf51af02..000000000000
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
-import static com.android.server.integrity.serializer.RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets;
-import static com.android.server.testutils.TestUtils.assertExpectException;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.integrity.AppInstallMetadata;
-import android.content.integrity.AtomicFormula;
-import android.content.integrity.CompoundFormula;
-import android.content.integrity.IntegrityFormula;
-import android.content.integrity.Rule;
-
-import androidx.annotation.NonNull;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
-@RunWith(JUnit4.class)
-public class RuleIndexingDetailsIdentifierTest {
-
- private static final String SAMPLE_APP_CERTIFICATE = "testcert";
- private static final String SAMPLE_INSTALLER_NAME = "com.test.installer";
- private static final String SAMPLE_INSTALLER_CERTIFICATE = "installercert";
- private static final String SAMPLE_PACKAGE_NAME = "com.test.package";
-
- private static final AtomicFormula ATOMIC_FORMULA_WITH_PACKAGE_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- SAMPLE_PACKAGE_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_APP_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- SAMPLE_APP_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_NAME =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE =
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERTIFICATE,
- /* isHashedValue= */ false);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_VERSION_CODE =
- new AtomicFormula.LongAtomicFormula(AtomicFormula.VERSION_CODE,
- AtomicFormula.EQ, 12);
- private static final AtomicFormula ATOMIC_FORMULA_WITH_ISPREINSTALLED =
- new AtomicFormula.BooleanAtomicFormula(
- AtomicFormula.PRE_INSTALLED, /* booleanValue= */
- true);
-
-
- private static final Rule RULE_WITH_PACKAGE_NAME =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_APP_CERTIFICATE =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE,
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- private static final Rule RULE_WITH_INSTALLER_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_INSTALLER_NAME,
- ATOMIC_FORMULA_WITH_INSTALLER_CERTIFICATE)),
- Rule.DENY);
-
- private static final Rule RULE_WITH_NONSTRING_RESTRICTIONS =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_VERSION_CODE,
- ATOMIC_FORMULA_WITH_ISPREINSTALLED)),
- Rule.DENY);
- public static final int INVALID_FORMULA_TAG = -1;
-
- @Test
- public void getIndexType_nullRule() {
- List<Rule> ruleList = null;
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */
- "Index buckets cannot be created for null rule list.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_invalidFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(new Rule(getInvalidFormula(), Rule.DENY));
-
- assertExpectException(
- IllegalArgumentException.class,
- /* expectedExceptionMessageRegex= */ "Malformed rule identified.",
- () -> splitRulesIntoIndexBuckets(ruleList));
- }
-
- @Test
- public void getIndexType_ruleContainingPackageNameFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_PACKAGE_NAME);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- // Verify the resulting map content.
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly(SAMPLE_PACKAGE_NAME);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get(SAMPLE_PACKAGE_NAME))
- .containsExactly(RULE_WITH_PACKAGE_NAME);
- }
-
- @Test
- public void getIndexType_ruleContainingAppCertificateFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_APP_CERTIFICATE);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(NOT_INDEXED)).isEmpty();
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet())
- .containsExactly(SAMPLE_APP_CERTIFICATE);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get(SAMPLE_APP_CERTIFICATE))
- .containsExactly(RULE_WITH_APP_CERTIFICATE);
- }
-
- @Test
- public void getIndexType_ruleWithUnindexedCompoundFormula() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_ruleContainingCompoundFormulaWithIntAndBoolean() {
- List<Rule> ruleList = new ArrayList();
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- @Test
- public void getIndexType_negatedRuleContainingPackageNameFormula() {
- Rule negatedRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.NOT,
- Arrays.asList(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- ATOMIC_FORMULA_WITH_PACKAGE_NAME,
- ATOMIC_FORMULA_WITH_APP_CERTIFICATE)))),
- Rule.DENY);
- List<Rule> ruleList = new ArrayList();
- ruleList.add(negatedRule);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
- assertThat(result.get(PACKAGE_NAME_INDEXED)).isEmpty();
- assertThat(result.get(APP_CERTIFICATE_INDEXED)).isEmpty();
- assertThat(result.get(NOT_INDEXED).get("N/A")).containsExactly(negatedRule);
- }
-
- @Test
- public void getIndexType_allRulesTogetherSplitCorrectly() {
- Rule packageNameRuleA = getRuleWithPackageName("aaa");
- Rule packageNameRuleB = getRuleWithPackageName("bbb");
- Rule packageNameRuleC = getRuleWithPackageName("ccc");
- Rule certificateRule1 = getRuleWithAppCertificate("cert1");
- Rule certificateRule2 = getRuleWithAppCertificate("cert2");
- Rule certificateRule3 = getRuleWithAppCertificate("cert3");
-
- List<Rule> ruleList = new ArrayList();
- ruleList.add(packageNameRuleB);
- ruleList.add(packageNameRuleC);
- ruleList.add(packageNameRuleA);
- ruleList.add(certificateRule3);
- ruleList.add(certificateRule2);
- ruleList.add(certificateRule1);
- ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
- ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
-
- Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
-
- assertThat(result.keySet())
- .containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
-
- // We check asserts this way to ensure ordering based on package name.
- assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
-
- // We check asserts this way to ensure ordering based on app certificate.
- assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
- "cert3");
-
- assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
- RULE_WITH_NONSTRING_RESTRICTIONS);
- }
-
- private Rule getRuleWithPackageName(String packageName) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private Rule getRuleWithAppCertificate(String certificate) {
- return new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- certificate,
- /* isHashedValue= */ false),
- ATOMIC_FORMULA_WITH_INSTALLER_NAME)),
- Rule.DENY);
- }
-
- private IntegrityFormula getInvalidFormula() {
- return new AtomicFormula(0) {
- @Override
- public int getTag() {
- return INVALID_FORMULA_TAG;
- }
-
- @Override
- public boolean matches(AppInstallMetadata appInstallMetadata) {
- return false;
- }
-
- @Override
- public boolean isAppCertificateFormula() {
- return false;
- }
-
- @Override
- public boolean isAppCertificateLineageFormula() {
- return false;
- }
-
- @Override
- public boolean isInstallerFormula() {
- return false;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object obj) {
- return super.equals(obj);
- }
-
- @NonNull
- @Override
- protected Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
-
- @Override
- public String toString() {
- return super.toString();
- }
-
- @Override
- protected void finalize() throws Throwable {
- super.finalize();
- }
- };
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
index 4a43c2e6c180..9d7b6a171bd4 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BackgroundInstallControlServiceTest.java
@@ -977,11 +977,19 @@ public final class BackgroundInstallControlServiceTest {
assertEquals(USER_ID_1, UserHandle.getUserId(uid));
mPackageListObserver.onPackageRemoved(PACKAGE_NAME_1, uid);
+ // Test that notifyAllCallbacks doesn't trigger for non-background-installed package
+ mPackageListObserver.onPackageRemoved(PACKAGE_NAME_3, uid);
mTestLooper.dispatchAll();
assertEquals(1, packages.size());
assertFalse(packages.contains(USER_ID_1, PACKAGE_NAME_1));
assertTrue(packages.contains(USER_ID_2, PACKAGE_NAME_2));
+
+ verify(mCallbackHelper)
+ .notifyAllCallbacks(
+ USER_ID_1,
+ PACKAGE_NAME_1,
+ BackgroundInstallControlService.INSTALL_EVENT_TYPE_UNINSTALL);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index b5724b5c0cc8..48bc9d7c51a1 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -21,10 +21,8 @@ import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
-import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.service.notification.NotificationListenerService.META_DATA_DEFAULT_AUTOBIND;
-import static com.android.server.notification.Flags.FLAG_NOTIFICATION_NLS_REBIND;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_COMPONENT;
import static com.android.server.notification.ManagedServices.APPROVAL_BY_PACKAGE;
import static com.android.server.notification.NotificationManagerService.privateSpaceFlagsEnabled;
@@ -65,14 +63,11 @@ import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IInterface;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
-import android.testing.TestableLooper;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -87,9 +82,7 @@ import com.android.server.UiServiceTestCase;
import com.google.android.collect.Lists;
-import org.junit.After;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
@@ -110,10 +103,7 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
-
public class ManagedServicesTest extends UiServiceTestCase {
- @Rule
- public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@Mock
private IPackageManager mIpm;
@@ -125,7 +115,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
private ManagedServices.UserProfiles mUserProfiles;
@Mock private DevicePolicyManager mDpm;
Object mLock = new Object();
- private TestableLooper mTestableLooper;
UserInfo mZero = new UserInfo(0, "zero", 0);
UserInfo mTen = new UserInfo(10, "ten", 0);
@@ -153,7 +142,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mTestableLooper = new TestableLooper(Looper.getMainLooper());
mContext.setMockPackageManager(mPm);
mContext.addMockSystemService(Context.USER_SERVICE, mUm);
@@ -211,11 +199,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
mIpm, APPROVAL_BY_COMPONENT);
}
- @After
- public void tearDown() throws Exception {
- mTestableLooper.destroy();
- }
-
@Test
public void testBackupAndRestore_migration() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
@@ -905,7 +888,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, true);
service.reregisterService(cn, 0);
@@ -936,7 +919,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a", 0, false);
service.reregisterService(cn, 0);
@@ -967,7 +950,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, true);
service.reregisterService(cn, 0);
@@ -998,7 +981,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
return true;
});
- mockServiceInfoWithMetaData(List.of(cn), service, pm, new ArrayMap<>());
+ mockServiceInfoWithMetaData(List.of(cn), service, new ArrayMap<>());
service.addApprovedList("a/a", 0, false);
service.reregisterService(cn, 0);
@@ -1070,78 +1053,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
- public void registerService_bindingDied_rebindIsClearedOnUserSwitch() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Switch to user 10
- service.onUserSwitched(10);
-
- // Check that the scheduled rebind for user 0 was cleared
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, never()).reregisterService(any(), anyInt());
- }
-
- @Test
- public void registerService_bindingDied_rebindIsExecutedAfterTimeout() throws Exception {
- Context context = mock(Context.class);
- PackageManager pm = mock(PackageManager.class);
- ApplicationInfo ai = new ApplicationInfo();
- ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
-
- when(context.getPackageName()).thenReturn(mPkg);
- when(context.getUserId()).thenReturn(mUser.getIdentifier());
- when(context.getPackageManager()).thenReturn(pm);
- when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
- APPROVAL_BY_PACKAGE);
- service = spy(service);
- ComponentName cn = ComponentName.unflattenFromString("a/a");
-
- // Trigger onBindingDied for component when registering
- // => will schedule a rebind in 10 seconds
- when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
- Object[] args = invocation.getArguments();
- ServiceConnection sc = (ServiceConnection) args[1];
- sc.onBindingDied(cn);
- return true;
- });
- service.registerService(cn, 0);
- assertThat(service.isBound(cn, 0)).isFalse();
-
- // Check that the scheduled rebind is run
- mTestableLooper.moveTimeForward(ManagedServices.ON_BINDING_DIED_REBIND_DELAY_MS);
- mTestableLooper.processAllMessages();
- verify(service, times(1)).reregisterService(eq(cn), eq(0));
- }
-
- @Test
public void testPackageUninstall_packageNoLongerInApprovedList() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1300,65 +1211,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
- public void testUpgradeAppNoIntentFilterNoRebind() throws Exception {
- Context context = spy(getContext());
- doReturn(true).when(context).bindServiceAsUser(any(), any(), anyInt(), any());
-
- ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
-
- List<String> packages = new ArrayList<>();
- packages.add("package");
- addExpectedServices(service, packages, 0);
-
- final ComponentName unapprovedComponent = ComponentName.unflattenFromString("package/C1");
- final ComponentName approvedComponent = ComponentName.unflattenFromString("package/C2");
-
- // Both components are approved initially
- mExpectedPrimaryComponentNames.clear();
- mExpectedPrimaryPackages.clear();
- mExpectedPrimaryComponentNames.put(0, "package/C1:package/C2");
- mExpectedSecondaryComponentNames.clear();
- mExpectedSecondaryPackages.clear();
-
- loadXml(service);
-
- //Component package/C1 loses serviceInterface intent filter
- ManagedServices.Config config = service.getConfig();
- when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt()))
- .thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = approvedComponent.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
-
- // Trigger package update
- service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
- assertFalse(service.isComponentEnabledForCurrentProfiles(unapprovedComponent));
- assertTrue(service.isComponentEnabledForCurrentProfiles(approvedComponent));
- }
-
- @Test
public void testSetPackageOrComponentEnabled() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -1371,21 +1223,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
"user10package1/K", "user10.3/Component", "user10package2/L",
"user10.4/Component"}));
- // mock permissions for services
- PackageManager pm = mock(PackageManager.class);
- when(getContext().getPackageManager()).thenReturn(pm);
- List<ComponentName> enabledComponents = List.of(
- ComponentName.unflattenFromString("package/Comp"),
- ComponentName.unflattenFromString("package/C2"),
- ComponentName.unflattenFromString("again/M4"),
- ComponentName.unflattenFromString("user10package/B"),
- ComponentName.unflattenFromString("user10/Component"),
- ComponentName.unflattenFromString("user10package1/K"),
- ComponentName.unflattenFromString("user10.3/Component"),
- ComponentName.unflattenFromString("user10package2/L"),
- ComponentName.unflattenFromString("user10.4/Component"));
- mockServiceInfoWithMetaData(enabledComponents, service, pm, new ArrayMap<>());
-
for (int userId : expectedEnabled.keySet()) {
ArrayList<String> expectedForUser = expectedEnabled.get(userId);
for (int i = 0; i < expectedForUser.size(); i++) {
@@ -1447,90 +1284,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
- @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
- public void testSetPackageOrComponentEnabled_pkgInstalledAfterEnabling() throws Exception {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
-
- final int userId = 0;
- final String validComponent = "again/M4";
- ArrayList<String> expectedEnabled = Lists.newArrayList("package/Comp", "package/C2",
- validComponent);
-
- PackageManager pm = mock(PackageManager.class);
- when(getContext().getPackageManager()).thenReturn(pm);
- service = spy(service);
-
- // Component again/M4 is a valid service and the package is available
- doReturn(true).when(service)
- .isValidService(ComponentName.unflattenFromString(validComponent), userId);
- when(pm.isPackageAvailable("again")).thenReturn(true);
-
- // "package" is not available and its services are not valid
- doReturn(false).when(service)
- .isValidService(ComponentName.unflattenFromString("package/Comp"), userId);
- doReturn(false).when(service)
- .isValidService(ComponentName.unflattenFromString("package/C2"), userId);
- when(pm.isPackageAvailable("package")).thenReturn(false);
-
- // Enable all components
- for (String component: expectedEnabled) {
- service.setPackageOrComponentEnabled(component, userId, true, true);
- }
-
- // Verify everything added is approved
- for (String component: expectedEnabled) {
- assertTrue("Not allowed: user: " + userId + " entry: " + component
- + " for approval level " + APPROVAL_BY_COMPONENT,
- service.isPackageOrComponentAllowed(component, userId));
- }
-
- // Add missing package "package"
- service.onPackagesChanged(false, new String[]{"package"}, new int[]{0});
-
- // Check that component of "package" are not enabled
- assertFalse(service.isComponentEnabledForCurrentProfiles(
- ComponentName.unflattenFromString("package/Comp")));
- assertFalse(service.isPackageOrComponentAllowed("package/Comp", userId));
-
- assertFalse(service.isComponentEnabledForCurrentProfiles(
- ComponentName.unflattenFromString("package/C2")));
- assertFalse(service.isPackageOrComponentAllowed("package/C2", userId));
-
- // Check that the valid components are still enabled
- assertTrue(service.isComponentEnabledForCurrentProfiles(
- ComponentName.unflattenFromString(validComponent)));
- assertTrue(service.isPackageOrComponentAllowed(validComponent, userId));
- }
-
- @Test
- @EnableFlags(FLAG_NOTIFICATION_NLS_REBIND)
- public void testSetPackageOrComponentEnabled_invalidComponent() throws Exception {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
- mIpm, APPROVAL_BY_COMPONENT);
-
- final int userId = 0;
- final String invalidComponent = "package/Comp";
-
- PackageManager pm = mock(PackageManager.class);
- when(getContext().getPackageManager()).thenReturn(pm);
- service = spy(service);
-
- // Component is an invalid service and the package is available
- doReturn(false).when(service)
- .isValidService(ComponentName.unflattenFromString(invalidComponent), userId);
- when(pm.isPackageAvailable("package")).thenReturn(true);
- service.setPackageOrComponentEnabled(invalidComponent, userId, true, true);
-
- // Verify that the component was not enabled
- assertFalse("Not allowed: user: " + userId + " entry: " + invalidComponent
- + " for approval level " + APPROVAL_BY_COMPONENT,
- service.isPackageOrComponentAllowed(invalidComponent, userId));
- assertFalse(service.isComponentEnabledForCurrentProfiles(
- ComponentName.unflattenFromString(invalidComponent)));
- }
-
- @Test
public void testGetAllowedPackages_byUser() throws Exception {
for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
@@ -2191,7 +1944,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindAllow.putBoolean(META_DATA_DEFAULT_AUTOBIND, true);
metaDatas.put(cn_allowed, metaDataAutobindAllow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_allowed.flattenToString(), 0, true);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2236,7 +1989,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2275,7 +2028,7 @@ public class ManagedServicesTest extends UiServiceTestCase {
metaDataAutobindDisallow.putBoolean(META_DATA_DEFAULT_AUTOBIND, false);
metaDatas.put(cn_disallowed, metaDataAutobindDisallow);
- mockServiceInfoWithMetaData(componentNames, service, pm, metaDatas);
+ mockServiceInfoWithMetaData(componentNames, service, metaDatas);
service.addApprovedList(cn_disallowed.flattenToString(), 0, true);
@@ -2346,8 +2099,8 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
private void mockServiceInfoWithMetaData(List<ComponentName> componentNames,
- ManagedServices service, PackageManager packageManager,
- ArrayMap<ComponentName, Bundle> metaDatas) throws RemoteException {
+ ManagedServices service, ArrayMap<ComponentName, Bundle> metaDatas)
+ throws RemoteException {
when(mIpm.getServiceInfo(any(), anyLong(), anyInt())).thenAnswer(
(Answer<ServiceInfo>) invocation -> {
ComponentName invocationCn = invocation.getArgument(0);
@@ -2362,39 +2115,6 @@ public class ManagedServicesTest extends UiServiceTestCase {
return null;
}
);
-
- // add components to queryIntentServicesAsUser response
- final List<String> packages = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- packages.add(cn.getPackageName());
- }
- ManagedServices.Config config = service.getConfig();
- when(packageManager.queryIntentServicesAsUser(any(), anyInt(), anyInt())).
- thenAnswer(new Answer<List<ResolveInfo>>() {
- @Override
- public List<ResolveInfo> answer(InvocationOnMock invocationOnMock)
- throws Throwable {
- Object[] args = invocationOnMock.getArguments();
- Intent invocationIntent = (Intent) args[0];
- if (invocationIntent != null) {
- if (invocationIntent.getAction().equals(config.serviceInterface)
- && packages.contains(invocationIntent.getPackage())) {
- List<ResolveInfo> dummyServices = new ArrayList<>();
- for (ComponentName cn: componentNames) {
- ResolveInfo resolveInfo = new ResolveInfo();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.packageName = invocationIntent.getPackage();
- serviceInfo.name = cn.getClassName();
- serviceInfo.permission = service.getConfig().bindPermission;
- resolveInfo.serviceInfo = serviceInfo;
- dummyServices.add(resolveInfo);
- }
- return dummyServices;
- }
- }
- return new ArrayList<>();
- }
- });
}
private void resetComponentsAndPackages() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
index 2c645e0ca353..0f7de7d78ccf 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java
@@ -28,7 +28,6 @@ import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertNull;
-
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.any;
@@ -198,8 +197,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testWriteXml_userTurnedOffNAS() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
-
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
@@ -435,10 +432,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception {
ComponentName component1 = ComponentName.unflattenFromString("package/Component1");
ComponentName component2 = ComponentName.unflattenFromString("package/Component2");
-
- doReturn(true).when(mAssistants).isValidService(eq(component1), eq(mZero.id));
- doReturn(true).when(mAssistants).isValidService(eq(component2), eq(mZero.id));
-
mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true,
true, true);
verify(mNm, never()).setNotificationAssistantAccessGrantedForUserInternal(
@@ -584,7 +577,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -608,7 +600,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState_readWriteXml_entries() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
@@ -632,7 +623,6 @@ public class NotificationAssistantsTest extends UiServiceTestCase {
public void testSetAdjustmentTypeSupportedState_readWriteXml_empty() throws Exception {
int userId = ActivityManager.getCurrentUser();
- doReturn(true).when(mAssistants).isValidService(eq(mCn), eq(userId));
mAssistants.loadDefaultsFromConfig(true);
mAssistants.setPackageOrComponentEnabled(mCn.flattenToString(), userId, true,
true, true);
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 cbfdc5f61e3f..d33317a7168e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -43,8 +43,8 @@ import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
-import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.DEFAULT_CHANNEL_ID;
+import static android.app.NotificationChannel.NEWS_ID;
import static android.app.NotificationChannel.PROMOTIONS_ID;
import static android.app.NotificationChannel.RECS_ID;
import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
@@ -78,7 +78,6 @@ import static android.app.PendingIntent.FLAG_ONE_SHOT;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_CONFIG;
-import static android.app.backup.NotificationLoggingConstants.DATA_TYPE_ZEN_RULES;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.PackageManager.FEATURE_TELECOM;
import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -336,12 +335,12 @@ import com.android.server.utils.quota.MultiRateLimiter;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
-import com.google.android.collect.Lists;
-import com.google.common.collect.ImmutableList;
-
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+import com.google.android.collect.Lists;
+import com.google.common.collect.ImmutableList;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -366,7 +365,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
-import java.io.OutputStream;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
@@ -14365,9 +14363,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.getSbn().getId(), r.getSbn().getTag(), r, false, false)).isTrue();
}
- private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
- boolean isImageBitmap, boolean isExpired) {
- Notification.Builder builder = new Notification.Builder(mContext);
+ private Notification createBigPictureNotification(boolean isBigPictureStyle, boolean hasImage,
+ boolean isImageBitmap) {
+ Notification.Builder builder = new Notification.Builder(mContext)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon);
Notification.BigPictureStyle style = new Notification.BigPictureStyle();
if (isBigPictureStyle && hasImage) {
@@ -14383,12 +14382,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
Notification notification = builder.setChannelId(TEST_CHANNEL_ID).build();
+ return notification;
+ }
+
+ private NotificationRecord createBigPictureRecord(boolean isBigPictureStyle, boolean hasImage,
+ boolean isImageBitmap, boolean isExpired) {
long timePostedMs = System.currentTimeMillis();
if (isExpired) {
timePostedMs -= BITMAP_DURATION.toMillis();
}
StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 8, "tag", mUid, 0,
- notification, UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
+ createBigPictureNotification(isBigPictureStyle, hasImage, isImageBitmap),
+ UserHandle.getUserHandleForUid(mUid), null, timePostedMs);
return new NotificationRecord(mContext, sbn, mTestNotificationChannel);
}
@@ -14400,6 +14405,33 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void testRemoveBitmaps_canRemoveRevokedDelegate() throws Exception {
+ Notification n = createBigPictureNotification(true, true, true);
+ long timePostedMs = System.currentTimeMillis();
+ timePostedMs -= BITMAP_DURATION.toMillis();
+
+ when(mPermissionHelper.hasPermission(UID_O)).thenReturn(true);
+ when(mPackageManagerInternal.isSameApp(PKG_O, UID_O, UserHandle.getUserId(UID_O)))
+ .thenReturn(true);
+ mService.mPreferencesHelper.createNotificationChannel(PKG_O, UID_O,
+ mTestNotificationChannel, true /* fromTargetApp */, false, UID_O,
+ false);
+ mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG_O, "old.delegate", 8, "tag",
+ UID_O, 0, n, UserHandle.getUserHandleForUid(UID_O), null, timePostedMs);
+
+ mService.addNotification(new NotificationRecord(mContext, sbn, mTestNotificationChannel));
+ mInternalService.removeBitmaps();
+
+ waitForIdle();
+
+ verify(mWorkerHandler, times(1))
+ .post(any(NotificationManagerService.EnqueueNotificationRunnable.class));
+ }
+
+ @Test
public void testRemoveBitmaps_notBigPicture_noRepost() {
addRecordAndRemoveBitmaps(
createBigPictureRecord(
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 ad11c2681779..25a8db6e6b9c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -123,51 +123,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
{"MEDIA_PLAY_PAUSE key -> Media Control",
new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
- KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
- {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
- KeyEvent.KEYCODE_B, META_ON},
- {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
- KeyEvent.KEYCODE_EXPLORER, 0},
- {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
- KeyEvent.KEYCODE_C, META_ON},
- {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
- KeyEvent.KEYCODE_CONTACTS, 0},
- {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
- KeyEvent.KEYCODE_E, META_ON},
- {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
- KeyEvent.KEYCODE_ENVELOPE, 0},
- {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
- KeyEvent.KEYCODE_K, META_ON},
- {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
- KeyEvent.KEYCODE_CALENDAR, 0},
- {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
- KeyEvent.KEYCODE_P, META_ON},
- {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
- KeyEvent.KEYCODE_MUSIC, 0},
- {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
- KeyEvent.KEYCODE_U, META_ON},
- {"CALCULATOR key -> Launch Default Calculator",
- new int[]{KeyEvent.KEYCODE_CALCULATOR},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
- KeyEvent.KEYCODE_CALCULATOR, 0},
- {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
- KeyEvent.KEYCODE_M, META_ON},
- {"Meta + S -> Launch Default Messaging App",
- new int[]{META_KEY, KeyEvent.KEYCODE_S},
- KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
- KeyEvent.KEYCODE_S, META_ON}};
+ KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0}};
}
@Keep
@@ -295,7 +251,51 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
KeyEvent.KEYCODE_DPAD_DOWN,
- META_ON | CTRL_ON}};
+ META_ON | CTRL_ON},
+ {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_B, META_ON},
+ {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+ KeyEvent.KEYCODE_EXPLORER, 0},
+ {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_C, META_ON},
+ {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+ KeyEvent.KEYCODE_CONTACTS, 0},
+ {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_E, META_ON},
+ {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+ KeyEvent.KEYCODE_ENVELOPE, 0},
+ {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_K, META_ON},
+ {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+ KeyEvent.KEYCODE_CALENDAR, 0},
+ {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_P, META_ON},
+ {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+ KeyEvent.KEYCODE_MUSIC, 0},
+ {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_U, META_ON},
+ {"CALCULATOR key -> Launch Default Calculator",
+ new int[]{KeyEvent.KEYCODE_CALCULATOR},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+ KeyEvent.KEYCODE_CALCULATOR, 0},
+ {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
+ KeyEvent.KEYCODE_M, META_ON},
+ {"Meta + S -> Launch Default Messaging App",
+ new int[]{META_KEY, KeyEvent.KEYCODE_S},
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
+ KeyEvent.KEYCODE_S, META_ON}};
}
@Keep
@@ -331,6 +331,7 @@ public class KeyGestureEventTests extends ShortcutKeyTestBase {
mPhoneWindowManager.setupAssistForLaunch();
mPhoneWindowManager.overrideTogglePanel();
mPhoneWindowManager.overrideInjectKeyEvent();
+ mPhoneWindowManager.overrideRoleManager();
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index d4ba3b25178d..9e7575f1c644 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -538,7 +538,7 @@ public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
public void testConsecutiveLaunchNewTask() {
final IBinder launchCookie = mock(IBinder.class);
final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
- mTrampolineActivity.noDisplay = true;
+ mTrampolineActivity.setIsNoDisplay(true);
mTrampolineActivity.mLaunchCookie = launchCookie;
mTrampolineActivity.mLaunchRootTask = launchRootTask;
onActivityLaunched(mTrampolineActivity);
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
index eacb8e9d628d..a0c5b54603f9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -25,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -190,14 +189,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -226,14 +220,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
eq(appWindow.getSurfaceControl()), anyFloat(),
eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS));
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -288,14 +277,9 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
@Test
@@ -352,13 +336,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase {
verify(appWindow.getPendingTransaction(), times(1)).setFrameRate(
appWindow.getSurfaceControl(), 60,
Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS);
- if (explicitRefreshRateHints()) {
- verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
- appWindow.getSurfaceControl(),
- SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
- } else {
- verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy(
- any(SurfaceControl.class), anyInt());
- }
+ verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy(
+ appWindow.getSurfaceControl(),
+ SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
index 3fa38bfe7185..3d08ca2905f3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java
@@ -21,14 +21,11 @@ import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.hardware.display.DisplayManager;
@@ -36,7 +33,6 @@ import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.view.Display.Mode;
import android.view.Surface;
-import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.test.filters.SmallTest;
@@ -274,97 +270,6 @@ public class RefreshRatePolicyTest extends WindowTestsBase {
}
@Test
- public void testAnimatingAppOverridePreferredModeId() {
- final WindowState overrideWindow = createWindow("overrideWindow");
- overrideWindow.mAttrs.packageName = "com.android.test";
- overrideWindow.mAttrs.preferredDisplayModeId = LOW_MODE_ID;
- parcelLayoutParams(overrideWindow);
- assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_LOW_EXACT, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- // Use default mode if it is animating by shell transition.
- overrideWindow.mActivityRecord.mSurfaceAnimator.cancelAnimation();
- registerTestTransitionPlayer();
- final Transition transition = overrideWindow.mTransitionController.createTransition(
- WindowManager.TRANSIT_OPEN);
- transition.collect(overrideWindow.mActivityRecord);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
-
- // If there will be display size change when switching from preferred mode to default mode,
- // then keep the current preferred mode during animating.
- mDisplayInfo = spy(mDisplayInfo);
- final Mode defaultMode = new Mode(4321 /* width */, 1234 /* height */, LOW_REFRESH_RATE);
- doReturn(defaultMode).when(mDisplayInfo).getDefaultMode();
- mPolicy = new RefreshRatePolicy(mWm, mDisplayInfo, mDenylist);
- assertEquals(LOW_MODE_ID, mPolicy.getPreferredModeId(overrideWindow));
- }
-
- @Test
- public void testAnimatingAppOverridePreferredRefreshRate() {
- final WindowState overrideWindow = createWindow("overrideWindow");
- overrideWindow.mAttrs.packageName = "com.android.test";
- overrideWindow.mAttrs.preferredRefreshRate = LOW_REFRESH_RATE;
- parcelLayoutParams(overrideWindow);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_LOW_PREFERRED, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- overrideWindow.mActivityRecord.mSurfaceAnimator.startAnimation(
- overrideWindow.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(overrideWindow));
- assertTrue(mPolicy.updateFrameRateVote(overrideWindow));
- assertEquals(FRAME_RATE_VOTE_NONE, overrideWindow.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(overrideWindow), FLOAT_TOLERANCE);
- }
-
- @Test
- public void testAnimatingDenylist() {
- final WindowState window = createWindow("overrideWindow");
- window.mAttrs.packageName = "com.android.test";
- parcelLayoutParams(window);
- when(mDenylist.isDenylisted("com.android.test")).thenReturn(true);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_DENY_LIST, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
-
- if (explicitRefreshRateHints()) {
- return;
- }
- window.mActivityRecord.mSurfaceAnimator.startAnimation(
- window.getPendingTransaction(), mock(AnimationAdapter.class),
- false /* hidden */, ANIMATION_TYPE_APP_TRANSITION);
- assertEquals(0, mPolicy.getPreferredModeId(window));
- assertTrue(mPolicy.updateFrameRateVote(window));
- assertEquals(FRAME_RATE_VOTE_NONE, window.mFrameRateVote);
- assertEquals(0, mPolicy.getPreferredMinRefreshRate(window), FLOAT_TOLERANCE);
- assertEquals(0, mPolicy.getPreferredMaxRefreshRate(window), FLOAT_TOLERANCE);
- }
-
- @Test
public void testAnimatingCamera() {
final WindowState cameraUsingWindow = createWindow("cameraUsingWindow");
cameraUsingWindow.mAttrs.packageName = "com.android.test";
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 1c878021c9e9..62a471166c5b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -4812,6 +4812,23 @@ public class SizeCompatTests extends WindowTestsBase {
assertFalse(mActivity.isResizeable());
assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
+
+ // Activity can opt-out the resizability by component level property.
+ final ComponentName name = getUniqueComponentName(mContext.getPackageName());
+ final PackageManager pm = mContext.getPackageManager();
+ spyOn(pm);
+ final PackageManager.Property property = new PackageManager.Property("propertyName",
+ true /* value */, name.getPackageName(), name.getClassName());
+ try {
+ doReturn(property).when(pm).getPropertyAsUser(
+ WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
+ name.getPackageName(), name.getClassName(), 0 /* userId */);
+ } catch (PackageManager.NameNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
+ .setComponent(name).setTask(mTask).build();
+ assertFalse(optOutActivity.isUniversalResizeable());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 3c921c612705..4568c77204a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -249,7 +249,7 @@ public class TaskLaunchParamsModifierTests extends
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverLaunchDisplayId = freeformDisplay.mDisplayId;
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
@@ -272,7 +272,7 @@ public class TaskLaunchParamsModifierTests extends
ActivityRecord reusableActivity = createSourceActivity(fullscreenDisplay);
ActivityRecord source = createSourceActivity(freeformDisplay);
source.mHandoverTaskDisplayArea = freeformDisplay.getDefaultTaskDisplayArea();
- source.noDisplay = true;
+ source.setIsNoDisplay(true);
assertEquals(RESULT_CONTINUE,
new CalculateRequestBuilder()
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index e8779c2b9ead..039a3ddd3e4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -51,7 +51,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
-import static com.android.window.flags.Flags.explicitRefreshRateHints;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1632,6 +1631,8 @@ public class TransitionTests extends WindowTestsBase {
transition.collect(taskA);
transition.setTransientLaunch(recent, taskA);
taskRecent.moveToFront("move-recent-to-front");
+ recent.setVisibility(true);
+ recent.setState(ActivityRecord.State.RESUMED, "test");
// During collecting and playing, the recent is on top so it is visible naturally.
// While B needs isTransientVisible to keep visibility because it is occluded by recents.
@@ -1644,15 +1645,21 @@ public class TransitionTests extends WindowTestsBase {
// Switch to another task. For example, use gesture navigation to switch tasks.
taskB.moveToFront("move-b-to-front");
+ appB.setVisibility(true);
// The previous app (taskA) should be paused first so it loses transient visible. Because
// visually it is taskA -> taskB, the pause -> resume order should be the same.
assertFalse(controller.isTransientVisible(taskA));
- // Keep the recent visible so there won't be 2 activities pausing at the same time. It is
- // to avoid the latency to resume the current top, i.e. appB.
- assertTrue(controller.isTransientVisible(taskRecent));
- // The recent is paused after the transient transition is finished.
- controller.finishTransition(ActionChain.testFinish(transition));
+ // The recent is occluded by appB.
assertFalse(controller.isTransientVisible(taskRecent));
+ // Active transient launch won't be paused if the transition is not finished. It is to
+ // avoid the latency to resume the current top (appB) by waiting for both recent and appA
+ // to complete pause.
+ assertEquals(recent, taskRecent.getResumedActivity());
+ assertFalse(taskRecent.startPausing(false /* uiSleeping */, appB /* resuming */, "test"));
+ // ActivityRecord#makeInvisible will add the invisible recent to the stopping list.
+ // So when the transition finished, the recent can still be notified to pause and stop.
+ mDisplayContent.ensureActivitiesVisible(null /* starting */, true /* notifyClients */);
+ assertTrue(mSupervisor.mStoppingActivities.contains(recent));
}
@Test
@@ -2883,17 +2890,14 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testTransitionsTriggerPerformanceHints() {
- final boolean explicitRefreshRateHints = explicitRefreshRateHints();
final var session = new SystemPerformanceHinter.HighPerfSession[1];
- if (explicitRefreshRateHints) {
- final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
- spyOn(perfHinter);
- doAnswer(invocation -> {
- session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
- spyOn(session[0]);
- return session[0];
- }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
- }
+ final SystemPerformanceHinter perfHinter = mWm.mSystemPerformanceHinter;
+ spyOn(perfHinter);
+ doAnswer(invocation -> {
+ session[0] = (SystemPerformanceHinter.HighPerfSession) invocation.callRealMethod();
+ spyOn(session[0]);
+ return session[0];
+ }).when(perfHinter).createSession(anyInt(), anyInt(), anyString());
final TransitionController controller = mDisplayContent.mTransitionController;
final TestTransitionPlayer player = registerTestTransitionPlayer();
final ActivityRecord app = new ActivityBuilder(mAtm).setCreateTask(true).build();
@@ -2905,15 +2909,11 @@ public class TransitionTests extends WindowTestsBase {
player.start();
verify(mDisplayContent).enableHighPerfTransition(true);
- if (explicitRefreshRateHints) {
- verify(session[0]).start();
- }
+ verify(session[0]).start();
player.finish();
verify(mDisplayContent).enableHighPerfTransition(false);
- if (explicitRefreshRateHints) {
- verify(session[0]).close();
- }
+ verify(session[0]).close();
}
@Test
diff --git a/services/usb/OWNERS b/services/usb/OWNERS
index d35dbb56437b..2dff392d4e34 100644
--- a/services/usb/OWNERS
+++ b/services/usb/OWNERS
@@ -1,9 +1,9 @@
-aprasath@google.com
-kumarashishg@google.com
-sarup@google.com
anothermark@google.com
+febinthattil@google.com
+aprasath@google.com
badhri@google.com
elaurent@google.com
albertccwang@google.com
jameswei@google.com
-howardyen@google.com \ No newline at end of file
+howardyen@google.com
+kumarashishg@google.com \ No newline at end of file
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 45a7fafa90a7..07969bd2cd43 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -304,11 +304,17 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
@Override
public void unloadModel(int modelHandle) {
synchronized (SoundTriggerModule.this) {
- int sessionId;
checkValid();
- sessionId = mLoadedModels.get(modelHandle).unload();
- mAudioSessionProvider.releaseSession(sessionId);
+ final var session = mLoadedModels.get(modelHandle).getSession();
+ mLoadedModels.remove(modelHandle);
+ mAudioSessionProvider.releaseSession(session.mSessionHandle);
}
+ // We don't need to post-synchronize on anything once the HAL has finished the unload
+ // and dispatched any appropriate callbacks -- since we don't do any state checking
+ // onModelUnloaded regardless.
+ // This is generally safe since there is no post-condition on the framework side when
+ // a model is unloaded. We assume that we won't ever have a modelHandle collision.
+ mHalService.unloadSoundModel(modelHandle);
}
@Override
@@ -402,6 +408,10 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
return mState;
}
+ private SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession getSession() {
+ return mSession;
+ }
+
private void setState(@NonNull ModelState state) {
mState = state;
SoundTriggerModule.this.notifyAll();
@@ -426,16 +436,6 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo
return mHandle;
}
- /**
- * Unloads the model.
- * @return The audio session handle.
- */
- private int unload() {
- mHalService.unloadSoundModel(mHandle);
- mLoadedModels.remove(mHandle);
- return mSession.mSessionHandle;
- }
-
private IBinder startRecognition(@NonNull RecognitionConfig config) {
if (mIsStopping == true) {
throw new RecoverableException(Status.INTERNAL_ERROR, "Race occurred");
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 9b83719402a0..be34619acac1 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -47,8 +47,6 @@ import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.SparseArray;
-import com.android.internal.annotations.VisibleForTesting;
-
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@@ -538,7 +536,13 @@ public final class SmsApplication {
}
private static String getDefaultSmsPackage(Context context, int userId) {
- return context.getSystemService(RoleManager.class).getSmsRoleHolder(userId);
+ // RoleManager might be null in unit tests running older mockito versions that do not
+ // support mocking final classes.
+ RoleManager roleManager = context.getSystemService(RoleManager.class);
+ if (roleManager == null) {
+ return "";
+ }
+ return roleManager.getSmsRoleHolder(userId);
}
/**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 0808cc3f1a75..2a06c3da0195 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -10153,6 +10153,15 @@ public class CarrierConfigManager {
"satellite_roaming_esos_inactivity_timeout_sec_int";
/**
+ * A string array containing the list of messaging package names that support satellite.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_SUPPORTED_MSG_APPS_STRING_ARRAY =
+ "satellite_supported_msg_apps_string_array";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
index 60cfc4876414..9a6f6b821d28 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializeException.java
+++ b/telephony/java/android/telephony/satellite/ISatelliteDisallowedReasonsCallback.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright 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.
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.server.integrity.serializer;
-
-import android.annotation.NonNull;
+package android.telephony.satellite;
/**
- * Thrown when rule serialization fails.
+ * Interface for satellite disallowed reason change callback.
+ *
+ * @hide
*/
-public class RuleSerializeException extends Exception {
- public RuleSerializeException(@NonNull String message) {
- super(message);
- }
-
- public RuleSerializeException(@NonNull String message, @NonNull Throwable cause) {
- super(message, cause);
- }
+oneway interface ISatelliteDisallowedReasonsCallback {
+ /**
+ * Indicates that disallowed reason of satellite has changed.
+ * @param disallowedReasons list of disallowed reasons.
+ */
+ void onSatelliteDisallowedReasonsChanged(in int[] disallowedReasons);
}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
new file mode 100644
index 000000000000..5e276aa49b05
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteDisallowedReasonsCallback.java
@@ -0,0 +1,38 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for disallowed reason of satellite change events.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface SatelliteDisallowedReasonsCallback {
+
+ /**
+ * Called when disallowed reason of satellite has changed.
+ * @param disallowedReasons Integer array of disallowed reasons.
+ */
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ void onSatelliteDisallowedReasonsChanged(@NonNull int[] disallowedReasons);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index be02232abe1b..7be3f337e43a 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -104,6 +104,11 @@ public final class SatelliteManager {
sSatelliteCommunicationAllowedStateCallbackMap =
new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap<SatelliteDisallowedReasonsCallback,
+ ISatelliteDisallowedReasonsCallback>
+ sSatelliteDisallowedReasonsCallbackMap =
+ new ConcurrentHashMap<>();
+
private final int mSubId;
/**
@@ -1487,6 +1492,47 @@ public final class SatelliteManager {
public @interface SatelliteCommunicationRestrictionReason {}
/**
+ * Satellite is disallowed because it is not supported.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED = 0;
+
+ /**
+ * Satellite is disallowed because it has not been provisioned.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED = 1;
+
+ /**
+ * Satellite is disallowed because it is currently outside an allowed region.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION = 2;
+
+ /**
+ * Satellite is disallowed because an unsupported default message application is being used.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP = 3;
+
+ /**
+ * Satellite is disallowed because location settings have been disabled.
+ * @hide
+ */
+ public static final int SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED = 4;
+
+ /** @hide */
+ @IntDef(prefix = "SATELLITE_DISALLOWED_REASON_", value = {
+ SATELLITE_DISALLOWED_REASON_NOT_SUPPORTED,
+ SATELLITE_DISALLOWED_REASON_NOT_PROVISIONED,
+ SATELLITE_DISALLOWED_REASON_NOT_IN_ALLOWED_REGION,
+ SATELLITE_DISALLOWED_REASON_UNSUPPORTED_DEFAULT_MSG_APP,
+ SATELLITE_DISALLOWED_REASON_LOCATION_DISABLED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SatelliteDisallowedReason {}
+
+ /**
* Start receiving satellite transmission updates.
* This can be called by the pointing UI when the user starts pointing to the satellite.
* Modem should continue to report the pointing input as the device or satellite moves.
@@ -2579,6 +2625,119 @@ public final class SatelliteManager {
}
/**
+ * Returns list of disallowed reasons of satellite.
+ *
+ * @return list of disallowed reasons of satellite.
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process isn't available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @SatelliteDisallowedReason
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ @NonNull
+ public List<Integer> getSatelliteDisallowedReasons() {
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ int[] receivedArray = telephony.getSatelliteDisallowedReasons();
+ if (receivedArray.length == 0) {
+ logd("receivedArray is empty, create empty list");
+ return new ArrayList<>();
+ } else {
+ return Arrays.stream(receivedArray).boxed().collect(Collectors.toList());
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("getSatelliteDisallowedReasons() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ return new ArrayList<>();
+ }
+
+ /**
+ * Registers for disallowed reasons change event from satellite service.
+ *
+ * @param executor The executor on which the callback will be called.
+ * @param callback The callback to handle disallowed reasons changed event.
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process is not available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void registerForSatelliteDisallowedReasonsChanged(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull SatelliteDisallowedReasonsCallback callback) {
+ Objects.requireNonNull(executor);
+ Objects.requireNonNull(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ ISatelliteDisallowedReasonsCallback internalCallback =
+ new ISatelliteDisallowedReasonsCallback.Stub() {
+ @Override
+ public void onSatelliteDisallowedReasonsChanged(
+ int[] disallowedReasons) {
+ executor.execute(() -> Binder.withCleanCallingIdentity(
+ () -> callback.onSatelliteDisallowedReasonsChanged(
+ disallowedReasons)));
+ }
+ };
+ telephony.registerForSatelliteDisallowedReasonsChanged(internalCallback);
+ sSatelliteDisallowedReasonsCallbackMap.put(callback, internalCallback);
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("registerForSatelliteDisallowedReasonsChanged() RemoteException" + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregisters for disallowed reasons change event from satellite service.
+ *
+ * @param callback The callback that was passed to
+ * {@link #registerForSatelliteDisallowedReasonsChanged(
+ * Executor, SatelliteDisallowedReasonsCallback)}
+ *
+ * @throws SecurityException if caller doesn't have required permission.
+ * @throws IllegalStateException if Telephony process is not available.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+ @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+ public void unregisterForSatelliteDisallowedReasonsChanged(
+ @NonNull SatelliteDisallowedReasonsCallback callback) {
+ Objects.requireNonNull(callback);
+ ISatelliteDisallowedReasonsCallback internalCallback =
+ sSatelliteDisallowedReasonsCallbackMap.remove(callback);
+
+ try {
+ ITelephony telephony = getITelephony();
+ if (telephony != null) {
+ if (internalCallback != null) {
+ telephony.unregisterForSatelliteDisallowedReasonsChanged(internalCallback);
+ } else {
+ loge("unregisterForSatelliteDisallowedReasonsChanged: No internal callback.");
+ throw new IllegalArgumentException("callback is not valid");
+ }
+ } else {
+ throw new IllegalStateException("Telephony service is null.");
+ }
+ } catch (RemoteException ex) {
+ loge("unregisterForSatelliteDisallowedReasonsChanged() RemoteException: " + ex);
+ ex.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Request to get the signal strength of the satellite connection.
*
* <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 231c8f551389..62cbb02c9fc7 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -71,6 +71,7 @@ import android.telephony.satellite.INtnSignalStrengthCallback;
import android.telephony.satellite.ISatelliteCapabilitiesCallback;
import android.telephony.satellite.ISatelliteCommunicationAllowedStateCallback;
import android.telephony.satellite.ISatelliteDatagramCallback;
+import android.telephony.satellite.ISatelliteDisallowedReasonsCallback;
import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
import android.telephony.satellite.ISatelliteProvisionStateCallback;
import android.telephony.satellite.ISatelliteSupportedStateCallback;
@@ -2955,6 +2956,37 @@ interface ITelephony {
in boolean needFullScreenPointingUI, IIntegerConsumer callback);
/**
+ * Returns integer array of disallowed reasons of satellite.
+ *
+ * @return Integer array of disallowed reasons of satellite.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ int[] getSatelliteDisallowedReasons();
+
+ /**
+ * Registers for disallowed reasons change event from satellite service.
+ *
+ * @param callback The callback to handle disallowed reasons changed event.
+ *
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void registerForSatelliteDisallowedReasonsChanged(
+ ISatelliteDisallowedReasonsCallback callback);
+
+ /**
+ * Unregisters for disallowed reasons change event from satellite service.
+ * If callback was not registered before, the request will be ignored.
+ *
+ * @param callback The callback to handle disallowed reasons changed event.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+ + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+ void unregisterForSatelliteDisallowedReasonsChanged(
+ ISatelliteDisallowedReasonsCallback callback);
+
+ /**
* Request to get whether satellite communication is allowed for the current location.
*
* @param subId The subId of the subscription to get whether satellite communication is allowed
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
new file mode 100644
index 000000000000..a3e5533599bc
--- /dev/null
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankTrackerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.app.jank.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.jank.Flags;
+import android.app.jank.JankTracker;
+import android.app.jank.StateTracker;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.view.Choreographer;
+import android.view.View;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public class JankTrackerTest {
+ private Choreographer mChoreographer;
+ private JankTracker mJankTracker;
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ /**
+ * Start an empty activity so decore view is not null when creating the JankTracker instance.
+ */
+ private static ActivityScenario<EmptyActivity> sEmptyActivityRule;
+
+ private static String sActivityName;
+
+ private static View sActivityDecorView;
+
+ @BeforeClass
+ public static void classSetup() {
+ sEmptyActivityRule = ActivityScenario.launch(EmptyActivity.class);
+ sEmptyActivityRule.onActivity(activity -> {
+ sActivityDecorView = activity.getWindow().getDecorView();
+ sActivityName = activity.toString();
+ });
+ }
+
+ @AfterClass
+ public static void classTearDown() {
+ sEmptyActivityRule.close();
+ }
+
+ @Before
+ @UiThreadTest
+ public void setup() {
+ mChoreographer = Choreographer.getInstance();
+ mJankTracker = new JankTracker(mChoreographer, sActivityDecorView);
+ mJankTracker.setActivityName(sActivityName);
+ }
+
+ /**
+ * When jank tracking is enabled the activity name should be added as a state to associate
+ * frames to it.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTracking_WhenEnabled_ActivityAdded() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ StateTracker.StateData firstState = stateData.getFirst();
+
+ assertEquals(sActivityName, firstState.mWidgetId);
+ }
+
+ /**
+ * No states should be added when tracking is disabled.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingDisabled_StatesShouldNot_BeAddedToTracker() {
+ mJankTracker.disableAppJankTracking();
+
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(0, stateData.size());
+ }
+
+ /**
+ * The activity name as well as the test state should be added for frame association.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_StatesShould_BeAddedToTracker() {
+ mJankTracker.forceListenerRegistration();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.addUiState("FAKE_CATEGORY", "FAKE_ID",
+ "FAKE_STATE");
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(2, stateData.size());
+ }
+
+ /**
+ * Activity state should only be added once even if jank tracking is enabled multiple times.
+ */
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_DETAILED_APP_JANK_METRICS_API)
+ public void jankTrackingEnabled_EnabledCalledTwice_ActivityStateOnlyAddedOnce() {
+ mJankTracker.enableAppJankTracking();
+
+ ArrayList<StateTracker.StateData> stateData = new ArrayList<>();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+
+ stateData.clear();
+
+ mJankTracker.enableAppJankTracking();
+ mJankTracker.getAllUiStates(stateData);
+
+ assertEquals(1, stateData.size());
+ }
+}
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 1c6bd1114c1c..332b9b832037 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -132,6 +132,24 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
}
+ private fun getMinimizeButtonForTheApp(caption: UiObject2?): UiObject2 {
+ return caption
+ ?.children
+ ?.find { it.resourceName.endsWith(MINIMIZE_BUTTON_VIEW) }
+ ?: error("Unable to find resource $MINIMIZE_BUTTON_VIEW\n")
+ }
+
+ fun minimizeDesktopApp(wmHelper: WindowManagerStateHelper, device: UiDevice) {
+ val caption = getCaptionForTheApp(wmHelper, device)
+ val minimizeButton = getMinimizeButtonForTheApp(caption)
+ minimizeButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withAppTransitionIdle()
+ .withWindowSurfaceDisappeared(innerHelper)
+ .waitForAndVerify()
+ }
+
/** Open maximize menu and click snap resize button on the app header for the given app. */
fun snapResizeDesktopApp(
wmHelper: WindowManagerStateHelper,
@@ -400,6 +418,7 @@ open class DesktopModeAppHelper(private val innerHelper: IStandardAppHelper) :
const val DESKTOP_MODE_BUTTON: String = "desktop_button"
const val SNAP_LEFT_BUTTON: String = "maximize_menu_snap_left_button"
const val SNAP_RIGHT_BUTTON: String = "maximize_menu_snap_right_button"
+ const val MINIMIZE_BUTTON_VIEW: String = "minimize_window"
val caption: BySelector
get() = By.res(SYSTEMUI_PACKAGE, CAPTION)
}
diff --git a/tests/Input/res/xml/bookmarks.xml b/tests/Input/res/xml/bookmarks.xml
new file mode 100644
index 000000000000..ba3f1871cdec
--- /dev/null
+++ b/tests/Input/res/xml/bookmarks.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright 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.
+ -->
+<bookmarks>
+ <!-- the key combinations for the following shortcuts must be in sync
+ with the key combinations sent by the test in KeyGestureControllerTests.java -->
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b" />
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c" />
+ <bookmark
+ category="android.intent.category.APP_EMAIL"
+ shortcut="e" />
+ <bookmark
+ category="android.intent.category.APP_CALENDAR"
+ shortcut="k" />
+ <bookmark
+ category="android.intent.category.APP_MAPS"
+ shortcut="m" />
+ <bookmark
+ category="android.intent.category.APP_MUSIC"
+ shortcut="p" />
+ <bookmark
+ role="android.app.role.SMS"
+ shortcut="s" />
+ <bookmark
+ category="android.intent.category.APP_CALCULATOR"
+ shortcut="u" />
+
+ <bookmark
+ role="android.app.role.BROWSER"
+ shortcut="b"
+ shift="true" />
+
+ <bookmark
+ category="android.intent.category.APP_CONTACTS"
+ shortcut="c"
+ shift="true" />
+
+ <bookmark
+ package="com.test"
+ class="com.test.BookmarkTest"
+ shortcut="j"
+ shift="true" />
+</bookmarks> \ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
index 01c56b7148cd..862886ce69d2 100644
--- a/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
+++ b/tests/Input/src/com/android/server/input/InputGestureManagerTests.kt
@@ -21,6 +21,7 @@ import android.hardware.input.InputManager
import android.hardware.input.KeyGestureEvent
import android.platform.test.annotations.Presubmit
import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -42,7 +43,7 @@ class InputGestureManagerTests {
@Before
fun setup() {
- inputGestureManager = InputGestureManager()
+ inputGestureManager = InputGestureManager(ApplicationProvider.getApplicationContext())
}
@Test
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index f2d3229a098c..6eb00457a1a6 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -95,8 +95,11 @@ class InputManagerServiceTests {
@get:Rule
val extendedMockitoRule =
- ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java)
- .mockStatic(PermissionChecker::class.java).build()!!
+ ExtendedMockitoRule.Builder(this)
+ .mockStatic(LocalServices::class.java)
+ .mockStatic(PermissionChecker::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
@get:Rule
val setFlagsRule = SetFlagsRule()
@@ -122,6 +125,9 @@ class InputManagerServiceTests {
@Mock
private lateinit var kbdController: InputManagerService.KeyboardBacklightControllerInterface
+ @Mock
+ private lateinit var kcm: KeyCharacterMap
+
private lateinit var service: InputManagerService
private lateinit var localService: InputManagerInternal
private lateinit var context: Context
@@ -171,6 +177,9 @@ class InputManagerServiceTests {
ExtendedMockito.doReturn(packageManagerInternal).`when` {
LocalServices.getService(eq(PackageManagerInternal::class.java))
}
+ ExtendedMockito.doReturn(kcm).`when` {
+ KeyCharacterMap.load(anyInt())
+ }
assertTrue("Local service must be registered", this::localService.isInitialized)
service.setWindowManagerCallbacks(wmCallbacks)
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index 0b147d63ed08..6c9f764bbdee 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -16,13 +16,16 @@
package com.android.server.input
+import android.app.role.RoleManager
import android.content.Context
import android.content.ContextWrapper
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
-import android.hardware.input.IInputManager
+import android.content.res.XmlResourceParser
import android.hardware.input.AidlKeyGestureEvent
import android.hardware.input.AppLaunchData
+import android.hardware.input.IInputManager
import android.hardware.input.IKeyGestureEventListener
import android.hardware.input.IKeyGestureHandler
import android.hardware.input.InputGestureData
@@ -34,14 +37,15 @@ import android.os.Process
import android.os.SystemClock
import android.os.SystemProperties
import android.os.test.TestLooper
-import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.Presubmit
import android.platform.test.flag.junit.SetFlagsRule
import android.view.InputDevice
+import android.view.KeyCharacterMap
import android.view.KeyEvent
import android.view.WindowManagerPolicyConstants.FLAG_INTERACTIVE
import androidx.test.core.app.ApplicationProvider
+import com.android.dx.mockito.inline.extended.ExtendedMockito
import com.android.internal.R
import com.android.internal.annotations.Keep
import com.android.internal.util.FrameworkStatsLog
@@ -99,7 +103,9 @@ class KeyGestureControllerTests {
@Rule
val extendedMockitoRule = ExtendedMockitoRule.Builder(this)
.mockStatic(FrameworkStatsLog::class.java)
- .mockStatic(SystemProperties::class.java).build()!!
+ .mockStatic(SystemProperties::class.java)
+ .mockStatic(KeyCharacterMap::class.java)
+ .build()!!
@JvmField
@Rule
@@ -116,6 +122,7 @@ class KeyGestureControllerTests {
private var currentPid = 0
private lateinit var context: Context
+ private lateinit var keyGestureController: KeyGestureController
private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
private lateinit var testLooper: TestLooper
private var events = mutableListOf<KeyGestureEvent>()
@@ -123,8 +130,6 @@ class KeyGestureControllerTests {
@Before
fun setup() {
context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
- Mockito.`when`(context.resources).thenReturn(resources)
- inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
setupInputDevices()
setupBehaviors()
testLooper = TestLooper()
@@ -139,11 +144,13 @@ class KeyGestureControllerTests {
}
private fun setupBehaviors() {
- Mockito.`when`(
- resources.getBoolean(
- com.android.internal.R.bool.config_enableScreenshotChord
- )
- ).thenReturn(true)
+ Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
+ Mockito.`when`(resources.getBoolean(R.bool.config_enableScreenshotChord)).thenReturn(true)
+ val testBookmarks: XmlResourceParser = context.resources.getXml(
+ com.android.test.input.R.xml.bookmarks
+ )
+ Mockito.`when`(resources.getXml(R.xml.bookmarks)).thenReturn(testBookmarks)
+ Mockito.`when`(context.resources).thenReturn(resources)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH))
.thenReturn(true)
Mockito.`when`(packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK))
@@ -152,6 +159,10 @@ class KeyGestureControllerTests {
}
private fun setupInputDevices() {
+ val correctIm = context.getSystemService(InputManager::class.java)!!
+ val virtualDevice = correctIm.getInputDevice(KeyCharacterMap.VIRTUAL_KEYBOARD)!!
+ val kcm = virtualDevice.keyCharacterMap!!
+ inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager)
val inputManager = InputManager(context)
Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
.thenReturn(inputManager)
@@ -159,9 +170,17 @@ class KeyGestureControllerTests {
val keyboardDevice = InputDevice.Builder().setId(DEVICE_ID).build()
Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice)
+ ExtendedMockito.`when`(KeyCharacterMap.load(Mockito.anyInt())).thenReturn(kcm)
}
- private fun notifyHomeGestureCompleted(keyGestureController: KeyGestureController) {
+ private fun setupKeyGestureController() {
+ keyGestureController = KeyGestureController(context, testLooper.looper)
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+ }
+
+ private fun notifyHomeGestureCompleted() {
keyGestureController.notifyKeyGestureCompleted(
DEVICE_ID, intArrayOf(KeyEvent.KEYCODE_H),
KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
@@ -171,12 +190,12 @@ class KeyGestureControllerTests {
@Test
fun testKeyGestureEvent_registerUnregisterListener() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val listener = KeyGestureEventListener()
// Register key gesture event listener
keyGestureController.registerKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted(keyGestureController)
+ notifyHomeGestureCompleted()
testLooper.dispatchAll()
assertEquals(
"Listener should get callbacks on key gesture event completed",
@@ -192,7 +211,7 @@ class KeyGestureControllerTests {
// Unregister listener
events.clear()
keyGestureController.unregisterKeyGestureEventListener(listener, 0)
- notifyHomeGestureCompleted(keyGestureController)
+ notifyHomeGestureCompleted()
testLooper.dispatchAll()
assertEquals(
"Listener should not get callback after being unregistered",
@@ -203,7 +222,7 @@ class KeyGestureControllerTests {
@Test
fun testKeyGestureEvent_multipleGestureHandlers() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
// Set up two callbacks.
var callbackCount1 = 0
@@ -267,7 +286,7 @@ class KeyGestureControllerTests {
}
@Keep
- private fun keyGestureEventHandlerTestArguments(): Array<TestData> {
+ private fun systemGesturesTestArguments(): Array<TestData> {
return arrayOf(
TestData(
"META + A -> Launch Assistant",
@@ -278,25 +297,6 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "RECENT_APPS -> Show Overview",
- intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
- 0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "APP_SWITCH -> App Switch",
- intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
- KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
- intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
- 0,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE
- )
- ),
- TestData(
"META + H -> Go Home",
intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_H),
KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
@@ -465,6 +465,379 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
+ "META + ALT -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + META -> Toggle Caps Lock",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + TAB -> Open Overview",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + TAB -> Toggle Recent Apps Switcher",
+ intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
+ intArrayOf(KeyEvent.KEYCODE_TAB),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
+ "CTRL + SPACE -> Switch Language Forward",
+ intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + SHIFT + SPACE -> Switch Language Backward",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_SPACE
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_SPACE),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "CTRL + ALT + Z -> Accessibility Shortcut",
+ intArrayOf(
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_Z
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
+ intArrayOf(KeyEvent.KEYCODE_Z),
+ KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + B -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_B),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + C -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_C),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + E -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_E),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_E),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
+ ),
+ TestData(
+ "META + K -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_K),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_K),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
+ ),
+ TestData(
+ "META + M -> Launch Default Maps",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_M),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_M),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MAPS)
+ ),
+ TestData(
+ "META + P -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_P),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_P),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
+ ),
+ TestData(
+ "META + S -> Launch Default SMS",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_S),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_S),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_SMS)
+ ),
+ TestData(
+ "META + U -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_U),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_U),
+ KeyEvent.META_META_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
+ ),
+ TestData(
+ "META + SHIFT + B -> Launch Default Browser",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_B
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_B),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
+ ),
+ TestData(
+ "META + SHIFT + C -> Launch Default Contacts",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_C
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_C),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
+ ),
+ TestData(
+ "META + SHIFT + J -> Launch Target Activity",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_SHIFT_LEFT,
+ KeyEvent.KEYCODE_J
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_J),
+ KeyEvent.META_META_ON or KeyEvent.META_SHIFT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForComponent("com.test", "com.test.BookmarkTest")
+ ),
+ TestData(
+ "META + CTRL + DEL -> Trigger Bug Report",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_DEL
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+ intArrayOf(KeyEvent.KEYCODE_DEL),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 3 -> Toggle Bounce Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_3
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_3),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 4 -> Toggle Mouse Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_4
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_4),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 5 -> Toggle Sticky Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_5
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_5),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "Meta + Alt + 6 -> Toggle Slow Keys",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_6
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
+ intArrayOf(KeyEvent.KEYCODE_6),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + CTRL + D -> Move a task to next display",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_CTRL_LEFT,
+ KeyEvent.KEYCODE_D
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
+ intArrayOf(KeyEvent.KEYCODE_D),
+ KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + [ -> Resizes a task to fit the left half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_LEFT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + ] -> Resizes a task to fit the right half of the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_RIGHT_BRACKET
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '=' -> Maximizes a task to fit the screen",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "ALT + '-' -> Restores a task size to its previous bounds",
+ intArrayOf(
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ )
+ )
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testKeyGestures(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
+ }
+
+ @Test
+ @Parameters(method = "systemGesturesTestArguments")
+ @EnableFlags(
+ com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
+ com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
+ com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
+ )
+ fun testCustomKeyGesturesNotAllowedForSystemGestures(test: TestData) {
+ setupKeyGestureController()
+ // Need to re-init so that bookmarks are correctly blocklisted
+ Mockito.`when`(iInputManager.getAppLaunchBookmarks())
+ .thenReturn(keyGestureController.appLaunchBookmarks)
+ keyGestureController.systemRunning()
+
+ val builder = InputGestureData.Builder()
+ .setKeyGestureType(test.expectedKeyGestureType)
+ .setTrigger(
+ InputGestureData.createKeyTrigger(
+ test.expectedKeys[0],
+ test.expectedModifierState
+ )
+ )
+ if (test.expectedAppLaunchData != null) {
+ builder.setAppLaunchData(test.expectedAppLaunchData)
+ }
+ assertEquals(
+ test.toString(),
+ InputManager.CUSTOM_INPUT_GESTURE_RESULT_ERROR_RESERVED_GESTURE,
+ keyGestureController.addCustomInputGesture(0, builder.build().aidlData)
+ )
+ }
+
+ @Keep
+ private fun systemKeysTestArguments(): Array<TestData> {
+ return arrayOf(
+ TestData(
+ "RECENT_APPS -> Show Overview",
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
+ intArrayOf(KeyEvent.KEYCODE_RECENT_APPS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "APP_SWITCH -> App Switch",
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
+ intArrayOf(KeyEvent.KEYCODE_APP_SWITCH),
+ 0,
+ intArrayOf(
+ KeyGestureEvent.ACTION_GESTURE_START,
+ KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ )
+ ),
+ TestData(
"BRIGHTNESS_UP -> Brightness Up",
intArrayOf(KeyEvent.KEYCODE_BRIGHTNESS_UP),
KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
@@ -553,101 +926,88 @@ class KeyGestureControllerTests {
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + ALT -> Toggle Caps Lock",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ "SYSRQ -> Take screenshot",
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+ intArrayOf(KeyEvent.KEYCODE_SYSRQ),
0,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "ALT + META -> Toggle Caps Lock",
- intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_META_LEFT),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_ALT_LEFT),
+ "ESC -> Close All Dialogs",
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
+ intArrayOf(KeyEvent.KEYCODE_ESCAPE),
0,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
),
TestData(
- "META + TAB -> Open Overview",
- intArrayOf(KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_TAB),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
- intArrayOf(KeyEvent.KEYCODE_TAB),
- KeyEvent.META_META_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- ),
- TestData(
- "ALT + TAB -> Toggle Recent Apps Switcher",
- intArrayOf(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_TAB),
- KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS_SWITCHER,
- intArrayOf(KeyEvent.KEYCODE_TAB),
- KeyEvent.META_ALT_ON,
- intArrayOf(
- KeyGestureEvent.ACTION_GESTURE_START,
- KeyGestureEvent.ACTION_GESTURE_COMPLETE
- )
+ "EXPLORER -> Launch Default Browser",
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_EXPLORER),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForRole(RoleManager.ROLE_BROWSER)
),
TestData(
- "CTRL + SPACE -> Switch Language Forward",
- intArrayOf(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_SPACE),
- KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
- intArrayOf(KeyEvent.KEYCODE_SPACE),
- KeyEvent.META_CTRL_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ "ENVELOPE -> Launch Default Email",
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_ENVELOPE),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_EMAIL)
),
TestData(
- "CTRL + SHIFT + SPACE -> Switch Language Backward",
- intArrayOf(
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_SHIFT_LEFT,
- KeyEvent.KEYCODE_SPACE
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
- intArrayOf(KeyEvent.KEYCODE_SPACE),
- KeyEvent.META_CTRL_ON or KeyEvent.META_SHIFT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ "CONTACTS -> Launch Default Contacts",
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CONTACTS),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CONTACTS)
),
TestData(
- "CTRL + ALT + Z -> Accessibility Shortcut",
- intArrayOf(
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_Z
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_SHORTCUT,
- intArrayOf(KeyEvent.KEYCODE_Z),
- KeyEvent.META_CTRL_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ "CALENDAR -> Launch Default Calendar",
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALENDAR),
+ 0,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALENDAR)
),
TestData(
- "SYSRQ -> Take screenshot",
- intArrayOf(KeyEvent.KEYCODE_SYSRQ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
- intArrayOf(KeyEvent.KEYCODE_SYSRQ),
+ "MUSIC -> Launch Default Music",
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_MUSIC),
0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_MUSIC)
),
TestData(
- "ESC -> Close All Dialogs",
- intArrayOf(KeyEvent.KEYCODE_ESCAPE),
- KeyGestureEvent.KEY_GESTURE_TYPE_CLOSE_ALL_DIALOGS,
- intArrayOf(KeyEvent.KEYCODE_ESCAPE),
+ "CALCULATOR -> Launch Default Calculator",
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
+ KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION,
+ intArrayOf(KeyEvent.KEYCODE_CALCULATOR),
0,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE),
+ AppLaunchData.createLaunchDataForCategory(Intent.CATEGORY_APP_CALCULATOR)
),
)
}
@Test
- @Parameters(method = "keyGestureEventHandlerTestArguments")
- fun testKeyGestures(test: TestData) {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(keyGestureController, test)
+ @Parameters(method = "systemKeysTestArguments")
+ fun testSystemKeys(test: TestData) {
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
}
@Test
fun testKeycodesFullyConsumed_irrespectiveOfHandlers() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val testKeys = intArrayOf(
KeyEvent.KEYCODE_RECENT_APPS,
KeyEvent.KEYCODE_APP_SWITCH,
@@ -675,7 +1035,7 @@ class KeyGestureControllerTests {
keyGestureController.registerKeyGestureHandler(handler, 0)
for (key in testKeys) {
- sendKeys(keyGestureController, intArrayOf(key), assertNotSentToApps = true)
+ sendKeys(intArrayOf(key), assertNotSentToApps = true)
}
}
@@ -683,9 +1043,8 @@ class KeyGestureControllerTests {
fun testSearchKeyGestures_defaultSearch() {
Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
.thenReturn(SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureNotProduced(
- keyGestureController,
"SEARCH -> Default Search",
intArrayOf(KeyEvent.KEYCODE_SEARCH),
)
@@ -695,9 +1054,8 @@ class KeyGestureControllerTests {
fun testSearchKeyGestures_searchActivity() {
Mockito.`when`(resources.getInteger(R.integer.config_searchKeyBehavior))
.thenReturn(SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SEARCH -> Launch Search Activity",
intArrayOf(KeyEvent.KEYCODE_SEARCH),
@@ -713,9 +1071,8 @@ class KeyGestureControllerTests {
fun testSettingKeyGestures_doNothing() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_NOTHING)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureNotProduced(
- keyGestureController,
"SETTINGS -> Do Nothing",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
)
@@ -725,9 +1082,8 @@ class KeyGestureControllerTests {
fun testSettingKeyGestures_settingsActivity() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SETTINGS -> Launch Settings Activity",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -743,9 +1099,8 @@ class KeyGestureControllerTests {
fun testSettingKeyGestures_notificationPanel() {
Mockito.`when`(resources.getInteger(R.integer.config_settingsKeyBehavior))
.thenReturn(SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL)
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
testKeyGestureInternal(
- keyGestureController,
TestData(
"SETTINGS -> Toggle Notification Panel",
intArrayOf(KeyEvent.KEYCODE_SETTINGS),
@@ -758,221 +1113,12 @@ class KeyGestureControllerTests {
}
@Test
- @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
- fun testTriggerBugReport() {
- Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "META + CTRL + DEL -> Trigger Bug Report",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_DEL
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
- intArrayOf(KeyEvent.KEYCODE_DEL),
- KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
- fun testTriggerBugReport_flagDisabled() {
- Mockito.`when`(SystemProperties.get("ro.debuggable")).thenReturn("1")
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "META + CTRL + DEL -> Not Trigger Bug Report (Fallback to BACK)",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_DEL
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
- intArrayOf(KeyEvent.KEYCODE_DEL),
- KeyEvent.META_META_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SHORTCUT_CONTROL,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_BOUNCE_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
- com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS
- )
- fun testKeyboardAccessibilityToggleShortcutPress() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 3 -> Toggle Bounce Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_3
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_BOUNCE_KEYS,
- intArrayOf(KeyEvent.KEYCODE_3),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 4 -> Toggle Mouse Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_4
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_MOUSE_KEYS,
- intArrayOf(KeyEvent.KEYCODE_4),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 5 -> Toggle Sticky Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_5
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_STICKY_KEYS,
- intArrayOf(KeyEvent.KEYCODE_5),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "Meta + Alt + 6 -> Toggle Slow Keys",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_6
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_SLOW_KEYS,
- intArrayOf(KeyEvent.KEYCODE_6),
- KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)))
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT)
- fun testMoveToNextDisplay() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "META + CTRL + D -> Move a task to next display",
- intArrayOf(
- KeyEvent.KEYCODE_META_LEFT,
- KeyEvent.KEYCODE_CTRL_LEFT,
- KeyEvent.KEYCODE_D
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MOVE_TO_NEXT_DISPLAY,
- intArrayOf(KeyEvent.KEYCODE_D),
- KeyEvent.META_META_ON or KeyEvent.META_CTRL_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testSnapLeftFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + [ -> Resizes a task to fit the left half of the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_LEFT_BRACKET
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_LEFT_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_LEFT_BRACKET),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testSnapRightFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + ] -> Resizes a task to fit the right half of the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_RIGHT_BRACKET
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_RIGHT_BRACKET),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testMaximizeFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + '=' -> Maximizes a task to fit the screen",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_EQUALS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
- intArrayOf(KeyEvent.KEYCODE_EQUALS),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
- @EnableFlags(com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS)
- fun testRestoreFreeformTask() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(
- keyGestureController,
- TestData(
- "ALT + '-' -> Restores a task size to its previous bounds",
- intArrayOf(
- KeyEvent.KEYCODE_ALT_LEFT,
- KeyEvent.KEYCODE_MINUS
- ),
- KeyGestureEvent.KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
- intArrayOf(KeyEvent.KEYCODE_MINUS),
- KeyEvent.META_ALT_ON,
- intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
- )
- }
-
- @Test
fun testCapsLockPressNotified() {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val listener = KeyGestureEventListener()
keyGestureController.registerKeyGestureEventListener(listener, 0)
- sendKeys(keyGestureController, intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
+ sendKeys(intArrayOf(KeyEvent.KEYCODE_CAPS_LOCK))
testLooper.dispatchAll()
assertEquals(
"Listener should get callbacks on key gesture event completed",
@@ -987,7 +1133,7 @@ class KeyGestureControllerTests {
}
@Keep
- private fun keyGestureEventHandlerTestArguments_forKeyCombinations(): Array<TestData> {
+ private fun systemGesturesTestArguments_forKeyCombinations(): Array<TestData> {
return arrayOf(
TestData(
"VOLUME_DOWN + POWER -> Screenshot Chord",
@@ -1048,14 +1194,14 @@ class KeyGestureControllerTests {
}
@Test
- @Parameters(method = "keyGestureEventHandlerTestArguments_forKeyCombinations")
+ @Parameters(method = "systemGesturesTestArguments_forKeyCombinations")
@EnableFlags(
com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER,
com.android.hardware.input.Flags.FLAG_USE_KEY_GESTURE_EVENT_HANDLER_MULTI_PRESS_GESTURES
)
fun testKeyCombinationGestures(test: TestData) {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
- testKeyGestureInternal(keyGestureController, test)
+ setupKeyGestureController()
+ testKeyGestureInternal(test)
}
@Keep
@@ -1096,7 +1242,7 @@ class KeyGestureControllerTests {
@Test
@Parameters(method = "customInputGesturesTestArguments")
fun testCustomKeyGestures(test: TestData) {
- val keyGestureController = KeyGestureController(context, testLooper.looper)
+ setupKeyGestureController()
val builder = InputGestureData.Builder()
.setKeyGestureType(test.expectedKeyGestureType)
.setTrigger(
@@ -1104,17 +1250,17 @@ class KeyGestureControllerTests {
test.expectedKeys[0],
test.expectedModifierState
)
- );
+ )
if (test.expectedAppLaunchData != null) {
builder.setAppLaunchData(test.expectedAppLaunchData)
}
- val inputGestureData = builder.build();
+ val inputGestureData = builder.build()
keyGestureController.addCustomInputGesture(0, inputGestureData.aidlData)
- testKeyGestureInternal(keyGestureController, test)
+ testKeyGestureInternal(test)
}
- private fun testKeyGestureInternal(keyGestureController: KeyGestureController, test: TestData) {
+ private fun testKeyGestureInternal(test: TestData) {
var handleEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handleEvents.add(KeyGestureEvent(event))
@@ -1123,7 +1269,7 @@ class KeyGestureControllerTests {
keyGestureController.registerKeyGestureHandler(handler, 0)
handleEvents.clear()
- sendKeys(keyGestureController, test.keys)
+ sendKeys(test.keys)
assertEquals(
"Test: $test doesn't produce correct number of key gesture events",
@@ -1162,11 +1308,7 @@ class KeyGestureControllerTests {
keyGestureController.unregisterKeyGestureHandler(handler, 0)
}
- private fun testKeyGestureNotProduced(
- keyGestureController: KeyGestureController,
- testName: String,
- testKeys: IntArray
- ) {
+ private fun testKeyGestureNotProduced(testName: String, testKeys: IntArray) {
var handleEvents = mutableListOf<KeyGestureEvent>()
val handler = KeyGestureHandler { event, _ ->
handleEvents.add(KeyGestureEvent(event))
@@ -1175,15 +1317,11 @@ class KeyGestureControllerTests {
keyGestureController.registerKeyGestureHandler(handler, 0)
handleEvents.clear()
- sendKeys(keyGestureController, testKeys)
+ sendKeys(testKeys)
assertEquals("Test: $testName should not produce Key gesture", 0, handleEvents.size)
}
- private fun sendKeys(
- keyGestureController: KeyGestureController,
- testKeys: IntArray,
- assertNotSentToApps: Boolean = false
- ) {
+ private fun sendKeys(testKeys: IntArray, assertNotSentToApps: Boolean = false) {
var metaState = 0
val now = SystemClock.uptimeMillis()
for (key in testKeys) {
@@ -1192,7 +1330,7 @@ class KeyGestureControllerTests {
DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD
)
- interceptKey(keyGestureController, downEvent, assertNotSentToApps)
+ interceptKey(downEvent, assertNotSentToApps)
metaState = metaState or MODIFIER.getOrDefault(key, 0)
downEvent.recycle()
@@ -1205,7 +1343,7 @@ class KeyGestureControllerTests {
DEVICE_ID, 0 /*scancode*/, 0 /*flags*/,
InputDevice.SOURCE_KEYBOARD
)
- interceptKey(keyGestureController, upEvent, assertNotSentToApps)
+ interceptKey(upEvent, assertNotSentToApps)
metaState = metaState and MODIFIER.getOrDefault(key, 0).inv()
upEvent.recycle()
@@ -1213,11 +1351,7 @@ class KeyGestureControllerTests {
}
}
- private fun interceptKey(
- keyGestureController: KeyGestureController,
- event: KeyEvent,
- assertNotSentToApps: Boolean
- ) {
+ private fun interceptKey(event: KeyEvent, assertNotSentToApps: Boolean) {
keyGestureController.interceptKeyBeforeQueueing(event, FLAG_INTERACTIVE)
testLooper.dispatchAll()